From 2a957919926f640b764ce340da60575ab9508725 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:28:22 -0700 Subject: [PATCH] =?UTF-8?q?simplify(compat):=20run=5Fagent/model=5Ftools/t?= =?UTF-8?q?oolsets/acp/providers=20=E2=80=94=20drop=2042=20re-exports/alia?= =?UTF-8?q?ses,=20repoint=2015=20callers=20+=2099=20test=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run_agent.py: delete the `# noqa: F401` re-export block (agent.process_bootstrap OpenAI/_SafeWriter/_get_proxy_*, model_tools get_tool_definitions/ handle_function_call/check_toolset_requirements, FailoverReason, _qwen_portal_headers/_routermint_headers, session_persistence names, estimate_request_tokens_rough, ContextCompressor + friends, jittered_backoff, prompt_builder names, message_sanitization names, tool_dispatch_helpers names) — 41 names run_agent never used itself — and the `_STREAM_DIAG_HEADERS` back-compat class alias (no in-tree reader). run_agent now imports only what it uses (get_toolset_for_tool, is_local_endpoint, coalesce/uniquify tool-call ids, cleanup_vm/get_active_env from terminal_tool_lifecycle). agent/*: `_ra().X` late-binds that only reached a re-export now import the defining module directly (agent_runtime_helpers -> process_bootstrap.OpenAI, model_tools.handle_function_call, session_persistence._safe_session_filename_component; agent_init -> model_tools.get_tool_definitions/check_toolset_requirements, _lazy_headers("agent.client_lifecycle", ...) for qwen/routermint; system_prompt -> agent.prompt_builder / model_tools directly, dropping its own _ra() shim and the `_r` parameter threading). `_ra()` stays for run_agent-resident names (logger, AIAgent, _hermes_home, _set_interrupt, ...). toolsets.py: remove resolve_multiple_toolsets (shim-only, restored by 34abf954bda); tests/test_toolsets.py pins the same union behavior via resolve_toolset over each name. providers/__init__.py: drop the OMIT_TEMPERATURE re-export (no callers via the package); ProviderProfile stays because __init__ uses it for annotations — 2 tests repointed to providers.base. agent/iteration_budget.py: drop the "run_agent re-exports the class" docstring pointer; 4 tests import IterationBudget from its home. model_tools.py (arg_coercion names), agent/tool_executor.py, and hermes_cli/cli_session_mixin.py repoints landed via a sibling commit on this shared worktree. Callers repointed: gateway/run.py, hermes_cli/cli_chat_turn_mixin.py, hermes_cli/cli_tui_mixin.py, tui_gateway/session_workdir.py, agent/transports/codex.py (one-line imports) + comment pointers in tools/file_state.py, tools/schema_sanitizer.py, scripts/tool_search_livetest.py. Tests: patch("run_agent.X") / monkeypatch.setattr(run_agent, "X") / `from run_agent import X` -> defining module across 99 test files. --- agent/agent_init.py | 15 ++--- agent/agent_runtime_helpers.py | 11 ++-- agent/iteration_budget.py | 1 - agent/process_bootstrap.py | 8 +-- agent/system_prompt.py | 26 +++----- agent/transports/codex.py | 2 +- gateway/run.py | 4 +- hermes_cli/cli_chat_turn_mixin.py | 2 +- hermes_cli/cli_tui_mixin.py | 2 +- providers/__init__.py | 2 +- run_agent.py | 36 ++--------- scripts/tool_search_livetest.py | 2 +- tests/agent/test_acp_provider_rails.py | 4 +- tests/agent/test_codex_cloudflare_headers.py | 4 +- .../test_load_time_durability_stamp_92231.py | 7 ++- tests/agent/test_model_metadata.py | 2 +- tests/agent/test_platform_hint_desktop.py | 6 +- .../test_profile_home_override_precedence.py | 2 +- tests/agent/test_provider_projection.py | 4 +- tests/agent/test_request_client_reuse.py | 2 +- tests/agent/test_skip_memory_store_65429.py | 6 +- tests/agent/test_studio_bridge_affinity.py | 6 +- tests/agent/test_system_prompt.py | 54 ++++++++-------- tests/cli/test_cli_interrupt_subagent.py | 2 +- tests/cli/test_focus_view.py | 8 +-- tests/cron/test_codex_execution_paths.py | 9 ++- .../test_cron_direct_api_call_watchdog.py | 2 +- .../hermes_cli/test_ollama_cloud_provider.py | 2 +- tests/providers/test_provider_registry.py | 2 +- .../test_1630_context_overflow_loop.py | 6 +- ...test_24996_fallback_exhaustion_cooldown.py | 6 +- .../test_32646_fallback_429_after_timeout.py | 8 +-- .../test_63425_credential_pool_auto_detect.py | 2 +- .../test_69078_image_corrupt_recovery.py | 10 +-- .../test_70773_shared_client_fd_corruption.py | 2 +- ...81641_text_turn_incremental_persistence.py | 6 +- .../test_92450_outer_error_retry_bound.py | 6 +- .../test_anthropic_third_party_oauth_guard.py | 10 +-- .../run_agent/test_api_max_retries_config.py | 2 +- .../run_agent/test_auth_provider_failover.py | 6 +- .../test_background_review_input_budget.py | 6 +- tests/run_agent/test_callable_api_key.py | 2 +- .../test_compression_budget_refund.py | 8 +-- .../run_agent/test_compression_feasibility.py | 8 +-- .../test_compression_timeout_terminal.py | 8 +-- .../run_agent/test_context_token_tracking.py | 4 +- .../test_continuation_ceiling_wedge.py | 6 +- .../test_continuation_repetition_guard.py | 6 +- .../test_conversation_fallback_state.py | 16 ++--- .../test_copilot_native_vision_headers.py | 2 +- ...eate_openai_client_disables_sdk_retries.py | 4 +- ...t_create_openai_client_kwargs_isolation.py | 2 +- .../test_create_openai_client_proxy_env.py | 7 ++- .../test_create_openai_client_reuse.py | 4 +- .../test_credential_pool_interrupt.py | 4 +- ...st_custom_provider_extra_headers_client.py | 2 +- tests/run_agent/test_dict_tool_call_args.py | 6 +- .../test_dropped_tool_call_recovery.py | 8 +-- .../test_fallback_api_mode_preservation.py | 6 +- tests/run_agent/test_first_chunk_at_hook.py | 6 +- .../test_gemini_native_reservation.py | 6 +- .../test_image_rejection_fallback.py | 2 +- .../test_init_fallback_on_exhausted_pool.py | 24 +++---- tests/run_agent/test_interactive_interrupt.py | 5 +- .../test_interpreter_shutdown_turn_exit.py | 4 +- .../test_invalid_context_length_warning.py | 6 +- tests/run_agent/test_iteration_budget_race.py | 10 +-- ...length_continuation_thinking_exhaustion.py | 6 +- .../test_malformed_tool_arguments.py | 8 +-- tests/run_agent/test_memory_provider_init.py | 12 ++-- .../run_agent/test_model_streaming_config.py | 20 +++--- .../test_nonretryable_error_html_summary.py | 6 +- .../test_nous_fallback_unavailable.py | 6 +- .../run_agent/test_openai_client_lifecycle.py | 10 +-- .../test_opencode_free_client_headers.py | 6 +- .../test_partial_stream_finish_reason.py | 12 ++-- .../test_per_model_threshold_init_ordering.py | 6 +- .../test_plugin_context_engine_init.py | 30 ++++----- .../test_post_tool_compression_attempt_cap.py | 8 +-- .../test_preflight_compression_cap_e2e.py | 6 +- .../test_preflight_timeout_typed_result.py | 6 +- .../run_agent/test_primary_runtime_restore.py | 62 +++++++++---------- .../test_provider_attribution_headers.py | 22 +++---- tests/run_agent/test_provider_fallback.py | 6 +- tests/run_agent/test_provider_parity.py | 6 +- .../test_repair_tool_call_arguments.py | 4 +- .../test_reset_aware_primary_restore.py | 14 ++--- .../test_run_agent_codex_responses.py | 19 +++--- tests/run_agent/test_sanitiser_escalation.py | 6 +- .../run_agent/test_sequential_tool_timeout.py | 12 ++-- tests/run_agent/test_steer.py | 6 +- .../test_stream_stale_breaker_reset.py | 6 +- tests/run_agent/test_streaming.py | 8 +-- .../test_streaming_tool_call_repair.py | 4 +- tests/run_agent/test_strict_api_validation.py | 6 +- tests/run_agent/test_switch_model_context.py | 6 +- .../test_thinking_prefill_trailing_turn.py | 6 +- .../test_token_persistence_non_cli.py | 6 +- tests/run_agent/test_tool_arg_coercion.py | 10 +-- .../run_agent/test_tool_batch_segmentation.py | 16 ++--- .../test_tool_call_guardrail_runtime.py | 24 +++---- .../test_tool_call_incremental_persistence.py | 20 +++--- .../test_tool_name_db_persistence.py | 6 +- .../test_turn_completion_explainer.py | 8 +-- .../test_verification_continuation_budget.py | 6 +- tests/run_interrupt_test.py | 5 +- tests/test_copilot_initiator.py | 6 +- tests/test_ollama_num_ctx.py | 6 +- tests/test_toolsets.py | 7 +-- .../tools/test_delegate_cron_sync_fallback.py | 6 +- tools/file_state.py | 2 +- tools/schema_sanitizer.py | 2 +- toolsets.py | 8 --- tui_gateway/session_workdir.py | 2 +- 114 files changed, 453 insertions(+), 491 deletions(-) diff --git a/agent/agent_init.py b/agent/agent_init.py index a12099a431..3ad54b13c2 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -1001,16 +1001,15 @@ def _lazy_headers(module: str, name: str, pass_key: bool = False, pass_base: boo # Host → default_headers factory for explicit base_url client construction. Ordered: first -# host match wins; no match falls back to the provider profile's declared headers. ``_ra()`` -# keeps the ``run_agent.*`` helpers patchable by tests. +# host match wins; no match falls back to the provider profile's declared headers. _HOST_DEFAULT_HEADERS: List[tuple[str, Callable[[Any, str], Dict[str, str]]]] = [ ("openrouter.ai", _lazy_headers("agent.auxiliary_client", "build_or_headers")), ("integrate.api.nvidia.com", _lazy_headers("agent.auxiliary_client", "build_nvidia_nim_headers", pass_base=True)), - ("api.routermint.com", lambda _k, _b: _ra()._routermint_headers()), + ("api.routermint.com", _lazy_headers("agent.client_lifecycle", "_routermint_headers")), ("githubcopilot.com", _lazy_headers("hermes_cli.models", "copilot_default_headers")), ("api.kimi.com", lambda _k, _b: {"User-Agent": "claude-code/0.1.0"}), - ("portal.qwen.ai", lambda _k, _b: _ra()._qwen_portal_headers()), + ("portal.qwen.ai", _lazy_headers("agent.client_lifecycle", "_qwen_portal_headers")), ("chatgpt.com", _lazy_headers("agent.codex_headers", "codex_cloudflare_headers", pass_key=True)), ("x.ai", _lazy_headers("tools.xai_http", "hermes_xai_default_headers")), ] @@ -1086,7 +1085,8 @@ def _load_tools(agent, enabled_toolsets, disabled_toolsets): agent._tool_snapshot_generation = _snapshot_registry._generation except Exception: agent._tool_snapshot_generation = 0 - agent.tools = _ra().get_tool_definitions( + import model_tools + agent.tools = model_tools.get_tool_definitions( enabled_toolsets=enabled_toolsets, disabled_toolsets=disabled_toolsets, quiet_mode=agent.quiet_mode, ) @@ -1105,7 +1105,8 @@ def _load_tools(agent, enabled_toolsets, disabled_toolsets): print(f" ✅ Enabled toolsets: {', '.join(enabled_toolsets)}") if disabled_toolsets: print(f" ❌ Disabled toolsets: {', '.join(disabled_toolsets)}") - requirements = _ra().check_toolset_requirements() + import model_tools + requirements = model_tools.check_toolset_requirements() missing_reqs = [name for name, available in requirements.items() if not available] if missing_reqs: print(f"⚠️ Some tools may not work due to missing requirements: {missing_reqs}") @@ -1970,7 +1971,7 @@ def _inject_context_engine_tools(agent): # Context engine tool schemas (lcm_*), deduped against existing names (plugins may # register the same schemas; duplicates 400 provider-side) and gated on enabled_toolsets # so `platform_toolsets: telegram: []` can't leak them. - # Skip names that are already present — the _ra().get_tool_definitions() quiet_mode cache returned a + # Skip names that are already present — the model_tools.get_tool_definitions() quiet_mode cache returned a # shared list pre-#17335, so a stray mutation here would poison subsequent agent inits in the same # Gateway process and trip provider-side 'duplicate tool name' errors. Even with the cache fix, dedup is # the right defense against plugin paths that may register the same schemas via ctx.register_tool(). diff --git a/agent/agent_runtime_helpers.py b/agent/agent_runtime_helpers.py index 8df9c243f4..613a9b03e7 100644 --- a/agent/agent_runtime_helpers.py +++ b/agent/agent_runtime_helpers.py @@ -1278,7 +1278,8 @@ def dump_api_request_debug( dump_payload["error"] = _api_error_debug_info(error) # Sanitize the session ID (may come from an untrusted X-Hermes-Session-Id header) so a # "../"-shaped ID cannot write outside logs_dir. - safe_sid = _ra()._safe_session_filename_component(agent.session_id) + from agent.session_persistence import _safe_session_filename_component + safe_sid = _safe_session_filename_component(agent.session_id) dump_file = agent.logs_dir / f"request_dump_{safe_sid}_{datetime.now().strftime('%Y%m%d_%H%M%S_%f')}.json" # Redact secrets first: this fires unconditionally on API errors and captures the full # request body, so context-embedded secrets would otherwise land in cleartext on disk. @@ -1750,8 +1751,9 @@ def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: boo client_kwargs, access_token=client_kwargs.get("api_key", ""), base_url=str(client_kwargs.get("base_url", "")), ) - # Module-level `OpenAI` is resolved lazily via __getattr__; tests patch `run_agent.OpenAI`. - client = _ra().OpenAI(**client_kwargs) + # ``process_bootstrap.OpenAI`` is a lazy SDK proxy; resolved at call time so tests can patch it. + from agent import process_bootstrap + client = process_bootstrap.OpenAI(**client_kwargs) _ra().logger.info("OpenAI client created (%s, shared=%s) %s", reason, shared, agent._client_log_context()) return client @@ -2247,7 +2249,8 @@ def invoke_tool(agent, function_name: str, function_args: dict, effective_task_i ) if skip_tool_execution_middleware: dispatch_kwargs["skip_tool_execution_middleware"] = True - return _ra().handle_function_call(function_name, next_args, effective_task_id, **dispatch_kwargs) + import model_tools + return model_tools.handle_function_call(function_name, next_args, effective_task_id, **dispatch_kwargs) if skip_tool_execution_middleware: return _execute(function_args) from hermes_cli.middleware import run_tool_execution_middleware diff --git a/agent/iteration_budget.py b/agent/iteration_budget.py index edcb32a101..1d1dfccdb6 100644 --- a/agent/iteration_budget.py +++ b/agent/iteration_budget.py @@ -3,7 +3,6 @@ Each ``AIAgent`` (parent or subagent) holds its own :class:`IterationBudget`: the parent's cap is ``max_iterations`` (default 500), each subagent's ``delegation.max_iterations`` (default 50), so total iterations across parent + subagents can exceed the parent's cap. -``run_agent`` re-exports the class so ``from run_agent import IterationBudget`` keeps working. """ from __future__ import annotations diff --git a/agent/process_bootstrap.py b/agent/process_bootstrap.py index 4416ec063f..dc7d1b7c52 100644 --- a/agent/process_bootstrap.py +++ b/agent/process_bootstrap.py @@ -1,9 +1,9 @@ """Process-level bootstrap helpers for ``run_agent``. Lazy OpenAI SDK import (``_OpenAIProxy`` keeps ``isinstance`` and -``patch("run_agent.OpenAI")`` working), crash-resistant stdio (``_SafeWriter``), -env-only HTTP proxy resolution, and Codex dual-stack (Happy Eyeballs) -connection racing. ``run_agent`` re-exports every name. +``patch("agent.process_bootstrap.OpenAI")`` working), crash-resistant stdio +(``_SafeWriter``), env-only HTTP proxy resolution, and Codex dual-stack +(Happy Eyeballs) connection racing. """ from __future__ import annotations @@ -451,7 +451,7 @@ def _install_safe_stdio() -> None: setattr(sys, stream_name, _SafeWriter(stream)) -# Drop-in for ``openai.OpenAI`` (also re-exported via ``run_agent``). +# Drop-in for ``openai.OpenAI``. OpenAI = _OpenAIProxy() diff --git a/agent/system_prompt.py b/agent/system_prompt.py index 92370a6a93..6b1229fa29 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -24,6 +24,7 @@ from agent.prompt_builder import ( SKILLS_GUIDANCE, STEER_CHANNEL_NOTE, TASK_COMPLETION_GUIDANCE, TELEGRAM_RICH_MESSAGES_HINT, TOOL_USE_ENFORCEMENT_GUIDANCE, TOOL_USE_ENFORCEMENT_MODELS, drain_truncation_warnings, ) +from agent import prompt_builder as _pb from agent.runtime_cwd import resolve_context_cwd from hermes_constants import get_default_hermes_root, get_hermes_home from utils import is_truthy_value @@ -36,13 +37,6 @@ _PLUGIN_SECTION_FRAME_RE = re.compile( _GATE_WORDS = {**dict.fromkeys(("true", "always", "yes", "on"), True), **dict.fromkeys(("false", "never", "no", "off"), False)} -def _ra(): - """Lazy ``run_agent`` handle: tests ``patch("run_agent.load_soul_md")`` etc., - so the helpers must be resolved through that namespace on every call.""" - import run_agent - return run_agent - - def _model_gate(setting: Any, model: Optional[str], default_models) -> bool: """Resolve a config gate: True/"true"-ish -> on, False/"false"-ish -> off, list -> case-insensitive model-substring match, anything else ("auto") -> @@ -301,18 +295,19 @@ def _tool_guidance_block(agent: Any) -> Optional[str]: return " ".join(g for g in tool_guidance if g) or None -def _skills_prompt(agent: Any, _r: Any) -> str: +def _skills_prompt(agent: Any) -> str: """Skills index (empty without skills tools). Focus mode demotes non-coding categories to names-only — never hidden, every name stays visible.""" if not any(name in agent.valid_tool_names for name in ['skills_list', 'skill_view', 'skill_manage']): return "" - avail_toolsets = {_r.get_toolset_for_tool(tool_name) for tool_name in agent.valid_tool_names} - {None, ""} + import model_tools + avail_toolsets = {model_tools.get_toolset_for_tool(tool_name) for tool_name in agent.valid_tool_names} - {None, ""} try: from agent.coding_context import coding_compact_skill_categories _compact_cats = coding_compact_skill_categories(platform=agent.platform, cwd=resolve_context_cwd()) except Exception: _compact_cats = frozenset() - return _r.build_skills_system_prompt(available_tools=agent.valid_tool_names, available_toolsets=avail_toolsets, + return _pb.build_skills_system_prompt(available_tools=agent.valid_tool_names, available_toolsets=avail_toolsets, compact_categories=_compact_cats or None, skills_dir_override=_agent_skills_dir(agent)) @@ -489,12 +484,12 @@ def _memory_parts(agent: Any) -> List[str]: return parts -def _identity_parts(agent: Any, _r: Any, ctx_len: Optional[int]) -> Tuple[List[str], bool]: +def _identity_parts(agent: Any, ctx_len: Optional[int]) -> Tuple[List[str], bool]: """SOUL.md (primary identity; cron keeps the persona while skipping cwd instructions, scoped to the agent's OWN home) or the default identity. Returns ``(parts, soul_loaded)``.""" wants_soul = agent.load_soul_identity or not agent.skip_context_files - _soul_content = _r.load_soul_md(ctx_len, home_override=_agent_home(agent)) if wants_soul else None + _soul_content = _pb.load_soul_md(ctx_len, home_override=_agent_home(agent)) if wants_soul else None return ([_soul_content], True) if _soul_content else ([DEFAULT_AGENT_IDENTITY], False) @@ -571,7 +566,7 @@ def _post_workspace_parts(agent: Any) -> List[str]: return parts -def _context_files_part(agent: Any, _r: Any, ctx_len: Optional[int], soul_loaded: bool) -> List[str]: +def _context_files_part(agent: Any, ctx_len: Optional[int], soul_loaded: bool) -> List[str]: """Project context files (AGENTS.md etc.) for the context tier. TERMINAL_CWD when set (gateway); None lets discovery fall back to the launch dir. The install-tree fallback is only legitimate for cli/tui where the launch dir @@ -580,7 +575,7 @@ def _context_files_part(agent: Any, _r: Any, ctx_len: Optional[int], soul_loaded if agent.skip_context_files: return [] launch_artifact = getattr(agent, "_context_cwd_is_launch_artifact", False) - return [_r.build_context_files_prompt( + return [_pb.build_context_files_prompt( cwd=None if launch_artifact else resolve_context_cwd(), skip_soul=soul_loaded, context_length=ctx_len, allow_install_tree_fallback=agent.platform in ("cli", "tui"), home_override=_agent_home(agent))] @@ -596,7 +591,6 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) (snapshot, remaining session-stable guidance, caller ``system_message``, context files) and ``volatile`` (skills index, memory, user profile, external memory block, timestamp line). Never re-rendered mid-session.""" - _r = _ra() # Model context window scales the context-file caps; stable per conversation. _cc_len = getattr(getattr(agent, "context_compressor", None), "context_length", None) _ctx_len = _cc_len if isinstance(_cc_len, int) and _cc_len > 0 else None @@ -614,7 +608,7 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) if "skill_view" in (agent.valid_tool_names or set()) and "- hermes-agent:" in skills_prompt: stable_parts[_help_guidance_slot] = HERMES_AGENT_HELP_GUIDANCE stable_parts.extend(_alibaba_identity_part(agent)) - stable_parts.append(_r.build_environment_hints()) + stable_parts.append(_pb.build_environment_hints()) # Coding posture: operating brief stays in the stable prefix; the live # git/workspace snapshot sits behind its own cache boundary, and the blocks # below it must keep their historical post-snapshot position. diff --git a/agent/transports/codex.py b/agent/transports/codex.py index 77578a5a7b..874aac658a 100644 --- a/agent/transports/codex.py +++ b/agent/transports/codex.py @@ -479,7 +479,7 @@ class ResponsesApiTransport(ProviderTransport): is_codex_backend: bool — chatgpt.com/backend-api/codex is_xai_responses: bool — xAI/Grok backend github_reasoning_extra: dict | None — Copilot reasoning params """ - from run_agent import DEFAULT_AGENT_IDENTITY + from agent.prompt_builder import DEFAULT_AGENT_IDENTITY instructions = params.get("instructions", "") payload_messages = messages diff --git a/gateway/run.py b/gateway/run.py index 23b7991008..98ce1d4a71 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -1218,7 +1218,7 @@ def _select_cached_agent_history( retention. """ if isinstance(live_history, list) and len(live_history) > len(persisted_history): - from run_agent import _is_ephemeral_scaffolding + from agent.session_persistence import _is_ephemeral_scaffolding has_unpersisted_row = any( isinstance(message, dict) and not message.get("_db_persisted") @@ -2916,7 +2916,7 @@ def _format_gateway_process_notification(evt: dict) -> "str | None": return text if evt_type == "async_delegation": - from tools.process_registry import format_process_notification + from tools.process_registry_notifications import format_process_notification return format_process_notification(evt) return None diff --git a/hermes_cli/cli_chat_turn_mixin.py b/hermes_cli/cli_chat_turn_mixin.py index 6892a284d1..8d706fd456 100644 --- a/hermes_cli/cli_chat_turn_mixin.py +++ b/hermes_cli/cli_chat_turn_mixin.py @@ -61,7 +61,7 @@ class CLIChatTurnMixin: if blocked is not None: return blocked # Lone surrogates (rich-text clipboard paste) crash the OpenAI SDK's JSON serialization. - from run_agent import _sanitize_surrogates + from agent.message_sanitization import _sanitize_surrogates message = _sanitize_surrogates(message) self._chat_stage_user_message(agent, message) diff --git a/hermes_cli/cli_tui_mixin.py b/hermes_cli/cli_tui_mixin.py index c26ea1de68..07338043b2 100644 --- a/hermes_cli/cli_tui_mixin.py +++ b/hermes_cli/cli_tui_mixin.py @@ -1610,7 +1610,7 @@ class CLITuiMixin: event.app.invalidate() if pasted_text: # Sanitize surrogates (Word/Google Docs paste) before writing. - from run_agent import _sanitize_surrogates + from agent.message_sanitization import _sanitize_surrogates pasted_text = _sanitize_surrogates(pasted_text) line_count = pasted_text.count('\n') buf = event.current_buffer diff --git a/providers/__init__.py b/providers/__init__.py index 939d05ebac..2a4ee8c536 100644 --- a/providers/__init__.py +++ b/providers/__init__.py @@ -38,7 +38,7 @@ import logging import sys from pathlib import Path -from providers.base import OMIT_TEMPERATURE, ProviderProfile # noqa: F401 +from providers.base import ProviderProfile logger = logging.getLogger(__name__) diff --git a/run_agent.py b/run_agent.py index e8a803a514..1766c4555d 100644 --- a/run_agent.py +++ b/run_agent.py @@ -87,10 +87,6 @@ def _gateway_origin_json(agent: "AIAgent") -> Optional[str]: return None -# Every F401-suppressed import below is a re-export reached via `patch("run_agent.")`, -# `from run_agent import X`, or `_ra().` from the agent/ helpers — keep them resolving here. -# `OpenAI` is a lazy proxy (SDK import costs ~240ms) that keeps `patch("run_agent.OpenAI")` working. -from agent.process_bootstrap import OpenAI, _SafeWriter, _get_proxy_for_base_url, _get_proxy_from_env # noqa: F401 from agent.iteration_budget import IterationBudget from hermes_cli.env_loader import load_hermes_dotenv from hermes_cli.timeouts import get_provider_request_timeout, get_provider_stale_timeout @@ -103,14 +99,13 @@ if not _loaded_env_paths: logger.info("No .env file found. Using system environment variables.") -from model_tools import get_tool_definitions, get_toolset_for_tool, handle_function_call, check_toolset_requirements # noqa: F401 +from model_tools import get_toolset_for_tool from tools.terminal_tool_lifecycle import cleanup_vm, get_active_env from tools.interrupt import set_interrupt as _set_interrupt from tools.browser_tool import cleanup_browser from agent.memory_provider import is_trivial_prompt -from agent.error_classifier import FailoverReason # noqa: F401 -from agent.client_lifecycle import ClientLifecycleMixin, _qwen_portal_headers, _routermint_headers # noqa: F401 +from agent.client_lifecycle import ClientLifecycleMixin from agent.stream_delivery import StreamDeliveryMixin from agent.status_output import StatusOutputMixin from agent.api_request_hooks import ApiRequestHooksMixin @@ -119,28 +114,15 @@ from agent.interrupt_control import InterruptControlMixin from agent.turn_explainers import TurnExplainersMixin from agent.activity_tracking import ActivityTrackingMixin from agent.rate_limit_credits import RateLimitCreditsMixin -from agent.session_persistence import ( # noqa: F401 - SessionPersistenceMixin, _DB_PERSISTED_MARKER, _EPHEMERAL_SCAFFOLDING_FLAGS, - _is_ephemeral_scaffolding, _safe_session_filename_component, -) +from agent.session_persistence import SessionPersistenceMixin from agent.compression_facade import CompressionFacadeMixin from agent.turn_facade import TurnFacadeMixin from agent.vision_message_prep import VisionMessagePrepMixin from agent.reasoning_params import ReasoningParamsMixin from agent.lazy_forward import forward as _forward, forward_static as _forward_static from agent.session_activity import ActivityProvenance -from agent.model_metadata import estimate_request_tokens_rough, is_local_endpoint # noqa: F401 -from agent.context_compressor import COMPRESSED_SUMMARY_METADATA_KEY, ContextCompressor, user_originated_turn_view # noqa: F401 -from agent.retry_utils import jittered_backoff # noqa: F401 -from agent.prompt_builder import ( # noqa: F401 - DEFAULT_AGENT_IDENTITY, build_skills_system_prompt, build_context_files_prompt, - build_environment_hints, load_soul_md, -) -from agent.message_sanitization import ( # noqa: F401 - _SURROGATE_RE, _sanitize_surrogates, _sanitize_structure_surrogates, _sanitize_messages_surrogates, - _escape_invalid_chars_in_json_strings, _repair_tool_call_arguments, _strip_non_ascii, - _sanitize_messages_non_ascii, _sanitize_tools_non_ascii, _looks_like_image_content_rejection, - _strip_images_from_messages, _sanitize_structure_non_ascii, +from agent.model_metadata import is_local_endpoint +from agent.message_sanitization import ( coalesce_tool_call_id as _sanitize_coalesce_tool_call_id, uniquify_tool_call_ids as _sanitize_uniquify_tool_call_ids, ) @@ -151,10 +133,6 @@ from agent.codex_responses_adapter import ( _summarize_user_message_for_log, ) from agent.tool_guardrails import ToolGuardrailDecision, append_toolguard_guidance, toolguard_synthetic_result -from agent.tool_dispatch_helpers import ( # noqa: F401 - _should_parallelize_tool_batch, _is_destructive_command, _extract_parallel_scope_path, _paths_overlap, - _append_subdir_hint_to_multimodal, _trajectory_normalize_msg, -) from utils import base_url_host_matches, base_url_hostname, env_float, model_forces_max_completion_tokens @@ -493,8 +471,6 @@ class AIAgent( self._codex_reasoning_replay_enabled = False return {"messages": stripped_messages, "items": stripped_items} - # Backward-compat alias; the list lives in ``agent.stream_diag.STREAM_DIAG_HEADERS``. - from agent.stream_diag import STREAM_DIAG_HEADERS as _STREAM_DIAG_HEADERS # noqa: E402 _stream_diag_init = _forward_static("agent.stream_diag", "stream_diag_init") _stream_diag_capture_response = _forward("agent.stream_diag", "stream_diag_capture_response") _flatten_exception_chain = _forward_static("agent.stream_diag", "flatten_exception_chain") @@ -955,7 +931,7 @@ class AIAgent( process_registry.kill_all(task_id=task_id) def release_computer_use() -> None: - from tools.computer_use.tool import release_computer_use_session + from tools.computer_use import release_computer_use_session release_computer_use_session(task_id) for step in (kill_processes, lambda: cleanup_vm(task_id), lambda: cleanup_browser(task_id), release_computer_use): diff --git a/scripts/tool_search_livetest.py b/scripts/tool_search_livetest.py index fa28406030..8cd775dddf 100644 --- a/scripts/tool_search_livetest.py +++ b/scripts/tool_search_livetest.py @@ -366,7 +366,7 @@ def run_one_scenario(scenario: Dict[str, Any], enabled: bool, out_dir: Path) -> n_registered = register_fake_tools() # Capture tool calls via a hook on the registry dispatch path. We use the - # registry hook (rather than the run_agent.handle_function_call binding, + # registry hook (rather than the model_tools.handle_function_call binding, # which is already cached by tool_executor) because the dispatch call is # the one place every underlying tool call lands. Bridge calls are # extracted from the message transcript after the run. diff --git a/tests/agent/test_acp_provider_rails.py b/tests/agent/test_acp_provider_rails.py index de5a63a471..39189ca347 100644 --- a/tests/agent/test_acp_provider_rails.py +++ b/tests/agent/test_acp_provider_rails.py @@ -51,8 +51,8 @@ def _agent(monkeypatch, base_url: str, **kwargs): from run_agent import AIAgent client = _FakeClient() - monkeypatch.setattr("run_agent.OpenAI", lambda **_kw: client) - monkeypatch.setattr("run_agent.get_tool_definitions", lambda *a, **k: []) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", lambda **_kw: client) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda *a, **k: []) agent = AIAgent( model="gpt-5", # a model that would normally trigger the Responses upgrade api_key="test-key", diff --git a/tests/agent/test_codex_cloudflare_headers.py b/tests/agent/test_codex_cloudflare_headers.py index 5e315788e7..9cd1e68a6d 100644 --- a/tests/agent/test_codex_cloudflare_headers.py +++ b/tests/agent/test_codex_cloudflare_headers.py @@ -99,7 +99,7 @@ class TestPrimaryClientWiring: """Credential-rotation / base-url change path must also emit codex headers.""" from run_agent import AIAgent token = _make_codex_jwt("acct-rotation") - with patch("run_agent.OpenAI") as mock_openai: + with patch("agent.process_bootstrap.OpenAI") as mock_openai: mock_openai.return_value = MagicMock() agent = AIAgent( api_key="placeholder-openrouter-key", @@ -124,7 +124,7 @@ class TestPrimaryClientWiring: """Switching AWAY from chatgpt.com must drop the codex headers.""" from run_agent import AIAgent token = _make_codex_jwt() - with patch("run_agent.OpenAI") as mock_openai: + with patch("agent.process_bootstrap.OpenAI") as mock_openai: mock_openai.return_value = MagicMock() agent = AIAgent( api_key=token, diff --git a/tests/agent/test_load_time_durability_stamp_92231.py b/tests/agent/test_load_time_durability_stamp_92231.py index 7bad8923b3..be4a524e41 100644 --- a/tests/agent/test_load_time_durability_stamp_92231.py +++ b/tests/agent/test_load_time_durability_stamp_92231.py @@ -20,7 +20,8 @@ from __future__ import annotations from pathlib import Path from types import SimpleNamespace -from hermes_state import SessionDB, _DB_PERSISTED_MARKER_KEY +from hermes_state import SessionDB +from agent.context_compressor import _DB_PERSISTED_MARKER as _DB_PERSISTED_MARKER_KEY from run_agent import AIAgent @@ -68,9 +69,9 @@ def test_marker_constant_in_sync() -> None: """ import agent.context_compressor as cc import agent.turn_finalizer as tf - import run_agent + import agent.session_persistence as sp - assert _DB_PERSISTED_MARKER_KEY == run_agent._DB_PERSISTED_MARKER + assert _DB_PERSISTED_MARKER_KEY == sp._DB_PERSISTED_MARKER assert _DB_PERSISTED_MARKER_KEY == cc._DB_PERSISTED_MARKER assert _DB_PERSISTED_MARKER_KEY == tf._DB_PERSISTED_MARKER diff --git a/tests/agent/test_model_metadata.py b/tests/agent/test_model_metadata.py index f2f954675b..3f8aaf5a83 100644 --- a/tests/agent/test_model_metadata.py +++ b/tests/agent/test_model_metadata.py @@ -1262,7 +1262,7 @@ class TestStripProviderPrefix: def test_registered_profile_name_and_alias_are_stripped(self, monkeypatch): import providers - from providers import ProviderProfile + from providers.base import ProviderProfile monkeypatch.setattr(providers, "_REGISTRY", {}) monkeypatch.setattr(providers, "_ALIASES", {}) diff --git a/tests/agent/test_platform_hint_desktop.py b/tests/agent/test_platform_hint_desktop.py index 42cb92b89e..8587b5fb48 100644 --- a/tests/agent/test_platform_hint_desktop.py +++ b/tests/agent/test_platform_hint_desktop.py @@ -26,9 +26,9 @@ from agent.system_prompt import ( def _stable_prompt(agent): with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value=""), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value=""), ): return build_system_prompt_parts(agent)["stable"] diff --git a/tests/agent/test_profile_home_override_precedence.py b/tests/agent/test_profile_home_override_precedence.py index 40da1fd804..5e0536508a 100644 --- a/tests/agent/test_profile_home_override_precedence.py +++ b/tests/agent/test_profile_home_override_precedence.py @@ -133,7 +133,7 @@ def test_full_prompt_scoped_to_bot_on_bare_thread(tmp_path, monkeypatch): def build(): with ( - patch("run_agent.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), ): result["prompt"] = build_system_prompt(agent) diff --git a/tests/agent/test_provider_projection.py b/tests/agent/test_provider_projection.py index ba300e55fb..e68639281e 100644 --- a/tests/agent/test_provider_projection.py +++ b/tests/agent/test_provider_projection.py @@ -157,10 +157,10 @@ def _run_turn(monkeypatch, *, projected, iterations): from run_agent import AIAgent monkeypatch.setattr( - "run_agent.OpenAI", + "agent.process_bootstrap.OpenAI", lambda **_kw: _FakeAgentProviderClient(projected, iterations), ) - monkeypatch.setattr("run_agent.get_tool_definitions", lambda *a, **k: []) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda *a, **k: []) agent = AIAgent( model="test-model", diff --git a/tests/agent/test_request_client_reuse.py b/tests/agent/test_request_client_reuse.py index 0dc0cc5418..10607870fa 100644 --- a/tests/agent/test_request_client_reuse.py +++ b/tests/agent/test_request_client_reuse.py @@ -33,7 +33,7 @@ class _StubClient: def _make_agent(provider="openai", base_url="https://api.openai.com/v1", model="gpt-5.4"): - with patch("run_agent.OpenAI") as mock_openai: + with patch("agent.process_bootstrap.OpenAI") as mock_openai: mock_openai.return_value = MagicMock() agent = AIAgent( api_key="sk-test", diff --git a/tests/agent/test_skip_memory_store_65429.py b/tests/agent/test_skip_memory_store_65429.py index c84dbab0da..47a979ea8a 100644 --- a/tests/agent/test_skip_memory_store_65429.py +++ b/tests/agent/test_skip_memory_store_65429.py @@ -27,9 +27,9 @@ class _FakeOpenAI: def _make_agent( monkeypatch, enabled_toolsets=None, disabled_toolsets=None, skip_memory=True ): - monkeypatch.setattr("run_agent.get_tool_definitions", lambda **kw: []) - monkeypatch.setattr("run_agent.check_toolset_requirements", lambda: {}) - monkeypatch.setattr("run_agent.OpenAI", _FakeOpenAI) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda **kw: []) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _FakeOpenAI) return AIAgent( api_key="test-key", base_url="http://test", diff --git a/tests/agent/test_studio_bridge_affinity.py b/tests/agent/test_studio_bridge_affinity.py index e7141855d1..9a947d569e 100644 --- a/tests/agent/test_studio_bridge_affinity.py +++ b/tests/agent/test_studio_bridge_affinity.py @@ -83,9 +83,9 @@ def db(tmp_path): def _bridge_agent(session_db, session_id: str, *, declared_key: str | None): """Construct an agent the way the Studio bridge pool does.""" with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/agent/test_system_prompt.py b/tests/agent/test_system_prompt.py index b204981cb3..f8fb28b797 100644 --- a/tests/agent/test_system_prompt.py +++ b/tests/agent/test_system_prompt.py @@ -47,9 +47,9 @@ def _captured_context_cwd(agent): return "" with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", side_effect=fake_context_files), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", side_effect=fake_context_files), ): build_system_prompt_parts(agent) return captured["cwd"] @@ -80,8 +80,8 @@ class TestContextFileCwd: _context_cwd_is_launch_artifact=True, ) with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), patch("agent.system_prompt.resolve_context_cwd", return_value=tmp_path), ): context = build_system_prompt_parts(agent)["context"] @@ -102,8 +102,8 @@ class TestContextFileCwd: _context_cwd_is_launch_artifact=False, ) with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), patch("agent.system_prompt.resolve_context_cwd", return_value=tmp_path), ): context = build_system_prompt_parts(agent)["context"] @@ -113,18 +113,18 @@ class TestContextFileCwd: def _stable_prompt(agent): with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value=""), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value=""), ): return build_system_prompt_parts(agent)["stable"] def _prompt_parts(agent): with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value=""), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value=""), ): return build_system_prompt_parts(agent) @@ -311,9 +311,9 @@ class TestNamedProfileHintIntegration: def test_build_system_prompt_records_stable_prefix(): agent = _make_agent() with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value="context"), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value="context"), ): prompt = build_system_prompt(agent) @@ -360,9 +360,9 @@ def test_coding_prompt_preserves_legacy_workspace_order(monkeypatch): )) with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value="CONTEXT_FILES"), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value="CONTEXT_FILES"), patch( "agent.coding_context.coding_system_prompt_parts", return_value=( @@ -504,11 +504,11 @@ def _build(builder, **overrides): """Run a build_* function with skills + context files present.""" agent = _make_agent(valid_tool_names=["skills_list"], **overrides) with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value=_CONTEXT), - patch("run_agent.get_toolset_for_tool", return_value=None), - patch("run_agent.build_skills_system_prompt", return_value=_SKILLS), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value=_CONTEXT), + patch("model_tools.get_toolset_for_tool", return_value=None), + patch("agent.prompt_builder.build_skills_system_prompt", return_value=_SKILLS), ): return builder(agent) @@ -710,9 +710,9 @@ def test_conversation_start_uses_session_start_not_build_time(monkeypatch): monkeypatch.setattr(system_prompt, "get_hermes_home", lambda: Path("/hermes")) with ( - patch("run_agent.load_soul_md", return_value=""), - patch("run_agent.build_environment_hints", return_value=""), - patch("run_agent.build_context_files_prompt", return_value="CONTEXT_FILES"), + patch("agent.prompt_builder.load_soul_md", return_value=""), + patch("agent.prompt_builder.build_environment_hints", return_value=""), + patch("agent.prompt_builder.build_context_files_prompt", return_value="CONTEXT_FILES"), patch( "agent.coding_context.coding_system_prompt_parts", return_value=([], [], []), diff --git a/tests/cli/test_cli_interrupt_subagent.py b/tests/cli/test_cli_interrupt_subagent.py index 7088bf30b7..dc9942e200 100644 --- a/tests/cli/test_cli_interrupt_subagent.py +++ b/tests/cli/test_cli_interrupt_subagent.py @@ -96,7 +96,7 @@ class TestCLISubagentInterrupt(unittest.TestCase): # Patch AIAgent to use our mock from tools.delegate_tool import _run_single_child - from run_agent import IterationBudget + from agent.iteration_budget import IterationBudget parent.iteration_budget = IterationBudget(max_total=100) diff --git a/tests/cli/test_focus_view.py b/tests/cli/test_focus_view.py index 295c680772..c8808be158 100644 --- a/tests/cli/test_focus_view.py +++ b/tests/cli/test_focus_view.py @@ -241,10 +241,10 @@ def _make_agent(tool_progress_mode: str): } ] with ( - patch("run_agent.get_tool_definitions", return_value=tool_defs), - patch("run_agent.check_toolset_requirements", return_value={}), + patch("model_tools.get_tool_definitions", return_value=tool_defs), + patch("model_tools.check_toolset_requirements", return_value={}), patch("hermes_cli.config.load_config", return_value={}), - patch("run_agent.OpenAI"), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", @@ -292,7 +292,7 @@ def _run_fake_turn(tool_progress_mode: str, dispatch_mode: str = "sequential"): return json.dumps({"ok": args["query"]}) with ( - patch("run_agent.handle_function_call", side_effect=fake_dispatch), + patch("model_tools.handle_function_call", side_effect=fake_dispatch), patch.object(agent, "_invoke_tool", side_effect=fake_dispatch), patch( "agent.tool_executor.maybe_persist_tool_result", diff --git a/tests/cron/test_codex_execution_paths.py b/tests/cron/test_codex_execution_paths.py index 8513ce695a..574e8f5446 100644 --- a/tests/cron/test_codex_execution_paths.py +++ b/tests/cron/test_codex_execution_paths.py @@ -17,8 +17,7 @@ from gateway.session import SessionSource def _patch_agent_bootstrap(monkeypatch): monkeypatch.setattr( - run_agent, - "get_tool_definitions", + "model_tools.get_tool_definitions", lambda **kwargs: [ { "type": "function", @@ -30,7 +29,7 @@ def _patch_agent_bootstrap(monkeypatch): } ], ) - monkeypatch.setattr(run_agent, "check_toolset_requirements", lambda: {}) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) def _codex_message_response(text: str): @@ -94,7 +93,7 @@ class _Codex401ThenSuccessAgent(run_agent.AIAgent): def test_cron_run_job_codex_path_handles_internal_401_refresh(monkeypatch): _patch_agent_bootstrap(monkeypatch) - monkeypatch.setattr(run_agent, "OpenAI", _FakeOpenAI) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _FakeOpenAI) monkeypatch.setattr(run_agent, "AIAgent", _Codex401ThenSuccessAgent) monkeypatch.setattr( "hermes_cli.runtime_provider.resolve_runtime_provider", @@ -125,7 +124,7 @@ def test_cron_run_job_codex_path_handles_internal_401_refresh(monkeypatch): def test_gateway_run_agent_codex_path_handles_internal_401_refresh(monkeypatch): _patch_agent_bootstrap(monkeypatch) - monkeypatch.setattr(run_agent, "OpenAI", _FakeOpenAI) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _FakeOpenAI) monkeypatch.setattr(run_agent, "AIAgent", _Codex401ThenSuccessAgent) monkeypatch.setattr( gateway_run, diff --git a/tests/cron/test_cron_direct_api_call_watchdog.py b/tests/cron/test_cron_direct_api_call_watchdog.py index bee17ecad2..ab8a716c40 100644 --- a/tests/cron/test_cron_direct_api_call_watchdog.py +++ b/tests/cron/test_cron_direct_api_call_watchdog.py @@ -248,7 +248,7 @@ def test_e2e_cron_turn_is_bounded_through_the_real_agent_routing(monkeypatch): wire = _StallingWireClient() agent = _build_cron_agent(monkeypatch) agent.client = wire - monkeypatch.setattr(run_agent, "OpenAI", lambda **_kwargs: wire) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", lambda **_kwargs: wire) monkeypatch.setattr( run_agent.AIAgent, "_force_close_tcp_sockets", diff --git a/tests/hermes_cli/test_ollama_cloud_provider.py b/tests/hermes_cli/test_ollama_cloud_provider.py index 142e4405da..19c0de347f 100644 --- a/tests/hermes_cli/test_ollama_cloud_provider.py +++ b/tests/hermes_cli/test_ollama_cloud_provider.py @@ -250,7 +250,7 @@ class TestOllamaCloudAgentInit: def test_ollama_cloud_agent_uses_chat_completions(self, monkeypatch): """Ollama Cloud falls through to chat_completions — no special elif needed.""" monkeypatch.setenv("OLLAMA_API_KEY", "test-key") - with patch("run_agent.OpenAI") as mock_openai: + with patch("agent.process_bootstrap.OpenAI") as mock_openai: mock_openai.return_value = MagicMock() from run_agent import AIAgent agent = AIAgent( diff --git a/tests/providers/test_provider_registry.py b/tests/providers/test_provider_registry.py index c31879dcba..dd3e06a2b3 100644 --- a/tests/providers/test_provider_registry.py +++ b/tests/providers/test_provider_registry.py @@ -1,6 +1,6 @@ import pytest -from providers import ProviderProfile +from providers.base import ProviderProfile import providers diff --git a/tests/run_agent/test_1630_context_overflow_loop.py b/tests/run_agent/test_1630_context_overflow_loop.py index b976f4375a..483236bc38 100644 --- a/tests/run_agent/test_1630_context_overflow_loop.py +++ b/tests/run_agent/test_1630_context_overflow_loop.py @@ -23,9 +23,9 @@ class TestGeneric400Heuristic: def _make_agent(self): """Create a minimal AIAgent for testing error handling.""" with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent a = AIAgent( diff --git a/tests/run_agent/test_24996_fallback_exhaustion_cooldown.py b/tests/run_agent/test_24996_fallback_exhaustion_cooldown.py index 2e86c795c5..5b0eaa97f3 100644 --- a/tests/run_agent/test_24996_fallback_exhaustion_cooldown.py +++ b/tests/run_agent/test_24996_fallback_exhaustion_cooldown.py @@ -23,9 +23,9 @@ from agent.chat_completion_helpers import _FALLBACK_EXHAUSTED_COOLDOWN_S def _make_agent(fallback_model=None): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_32646_fallback_429_after_timeout.py b/tests/run_agent/test_32646_fallback_429_after_timeout.py index 3ba363e4f0..db5f045040 100644 --- a/tests/run_agent/test_32646_fallback_429_after_timeout.py +++ b/tests/run_agent/test_32646_fallback_429_after_timeout.py @@ -46,9 +46,9 @@ def _make_tool_defs(): def _make_agent_with_fallback(fb_chain): """Build a minimal AIAgent with the given fallback chain configured.""" with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs()), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs()), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), ): agent = AIAgent( api_key="primary-key-abcdef12", @@ -264,7 +264,7 @@ class TestFallbackChainResetOnTransportRecovery: patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch("agent.agent_runtime_helpers.time.sleep"), patch( "agent.auxiliary_client.resolve_provider_client", diff --git a/tests/run_agent/test_63425_credential_pool_auto_detect.py b/tests/run_agent/test_63425_credential_pool_auto_detect.py index b89edf76cd..3f78043cc8 100644 --- a/tests/run_agent/test_63425_credential_pool_auto_detect.py +++ b/tests/run_agent/test_63425_credential_pool_auto_detect.py @@ -36,7 +36,7 @@ class TestCredentialPoolPreservedOnAutoDetect: pool = SimpleNamespace(provider="anthropic") with patch("agent.auxiliary_client.resolve_provider_client", return_value=(None, None)), \ - patch("run_agent.get_tool_definitions", return_value=[]), \ + patch("model_tools.get_tool_definitions", return_value=[]), \ patch('agent.anthropic_adapter.build_anthropic_client', return_value=MagicMock()), \ patch('agent.anthropic_credentials.resolve_anthropic_token', return_value=''), \ patch('agent.anthropic_credentials._is_oauth_token', return_value=False), \ diff --git a/tests/run_agent/test_69078_image_corrupt_recovery.py b/tests/run_agent/test_69078_image_corrupt_recovery.py index c3b76ae8fe..51b834fa1a 100644 --- a/tests/run_agent/test_69078_image_corrupt_recovery.py +++ b/tests/run_agent/test_69078_image_corrupt_recovery.py @@ -218,9 +218,9 @@ def _make_agent(): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), ): agent = AIAgent( api_key="fx", # unused — the OpenAI client is mocked below @@ -291,7 +291,7 @@ class TestRunConversationRecoversFromCorruptImage400: patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch("agent.agent_runtime_helpers.time.sleep"), patch("agent.model_metadata.get_model_context_length", return_value=200000), ): @@ -382,7 +382,7 @@ class TestRunConversationRecoversFromCorruptImage400: patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch("agent.agent_runtime_helpers.time.sleep"), patch("agent.model_metadata.get_model_context_length", return_value=200000), ): diff --git a/tests/run_agent/test_70773_shared_client_fd_corruption.py b/tests/run_agent/test_70773_shared_client_fd_corruption.py index 1f6234a9ed..27839b901f 100644 --- a/tests/run_agent/test_70773_shared_client_fd_corruption.py +++ b/tests/run_agent/test_70773_shared_client_fd_corruption.py @@ -218,7 +218,7 @@ class TestReplacePrimaryRetiresInsteadOfClosing: agent, "_force_close_tcp_sockets", side_effect=lambda c: shutdown_calls.append(c) or 1, ): - with patch("run_agent.OpenAI", MagicMock()): + with patch("agent.process_bootstrap.OpenAI", MagicMock()): ok = agent._replace_primary_openai_client(reason="test_rotate") assert ok diff --git a/tests/run_agent/test_81641_text_turn_incremental_persistence.py b/tests/run_agent/test_81641_text_turn_incremental_persistence.py index 271ce4bc18..0f6dceb4ea 100644 --- a/tests/run_agent/test_81641_text_turn_incremental_persistence.py +++ b/tests/run_agent/test_81641_text_turn_incremental_persistence.py @@ -39,9 +39,9 @@ def loop_agent(): """AIAgent with a mocked OpenAI client (mirrors test_run_agent's fixture).""" from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_92450_outer_error_retry_bound.py b/tests/run_agent/test_92450_outer_error_retry_bound.py index 17e277da81..614a591d23 100644 --- a/tests/run_agent/test_92450_outer_error_retry_bound.py +++ b/tests/run_agent/test_92450_outer_error_retry_bound.py @@ -42,9 +42,9 @@ def loop_agent(): from tests.run_agent.test_run_agent import _mock_response with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_anthropic_third_party_oauth_guard.py b/tests/run_agent/test_anthropic_third_party_oauth_guard.py index 031ee0ca71..49d18eb8bb 100644 --- a/tests/run_agent/test_anthropic_third_party_oauth_guard.py +++ b/tests/run_agent/test_anthropic_third_party_oauth_guard.py @@ -34,9 +34,9 @@ _API_KEY_TOKEN = "sk-ant-api-abcdef1234567890" def agent(): """Minimal AIAgent construction, skipping tool discovery.""" with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", @@ -105,8 +105,8 @@ class TestOAuthFlagOnConstruction: def test_minimax_init_does_not_flip_oauth(self): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), patch("agent.anthropic_adapter.build_anthropic_client", return_value=MagicMock()), # Simulate a stale ANTHROPIC_TOKEN in the env — the init code diff --git a/tests/run_agent/test_api_max_retries_config.py b/tests/run_agent/test_api_max_retries_config.py index 5060c5e805..75a0682d24 100644 --- a/tests/run_agent/test_api_max_retries_config.py +++ b/tests/run_agent/test_api_max_retries_config.py @@ -16,7 +16,7 @@ def _make_agent(api_max_retries=None): if api_max_retries is not None: cfg["agent"]["api_max_retries"] = api_max_retries - with patch("run_agent.OpenAI"), \ + with patch("agent.process_bootstrap.OpenAI"), \ patch("hermes_cli.config.load_config", return_value=cfg), \ patch("hermes_cli.config.load_config_readonly", return_value=cfg): return AIAgent( diff --git a/tests/run_agent/test_auth_provider_failover.py b/tests/run_agent/test_auth_provider_failover.py index 4de397d9ba..c827ec51a4 100644 --- a/tests/run_agent/test_auth_provider_failover.py +++ b/tests/run_agent/test_auth_provider_failover.py @@ -24,9 +24,9 @@ from agent.turn_retry_state import TurnRetryState def _make_agent(fallback_model=None): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_background_review_input_budget.py b/tests/run_agent/test_background_review_input_budget.py index 40a9ad279b..b0d483f93d 100644 --- a/tests/run_agent/test_background_review_input_budget.py +++ b/tests/run_agent/test_background_review_input_budget.py @@ -75,9 +75,9 @@ def _tool_definition() -> dict: def _make_loop_agent(): with ( - patch("run_agent.get_tool_definitions", return_value=[_tool_definition()]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[_tool_definition()]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("agent.model_metadata.get_model_context_length", return_value=256_000), patch("agent.context_compressor.get_model_context_length", return_value=256_000), ): diff --git a/tests/run_agent/test_callable_api_key.py b/tests/run_agent/test_callable_api_key.py index 7870954ba4..ad0b109da8 100644 --- a/tests/run_agent/test_callable_api_key.py +++ b/tests/run_agent/test_callable_api_key.py @@ -51,7 +51,7 @@ class TestCreateOpenAIClientCallable: return MagicMock(api_key=kwargs.get("api_key")) # Patch the module-level OpenAI proxy used by ``_create_openai_client``. - monkeypatch.setattr("run_agent.OpenAI", fake_openai) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", fake_openai) # Build a minimal stand-in for AIAgent so we can call the bound # method directly without paying the full __init__ cost. diff --git a/tests/run_agent/test_compression_budget_refund.py b/tests/run_agent/test_compression_budget_refund.py index 4235cd1c7d..36c73f3afb 100644 --- a/tests/run_agent/test_compression_budget_refund.py +++ b/tests/run_agent/test_compression_budget_refund.py @@ -172,9 +172,9 @@ def _coherent_compressor() -> MagicMock: @pytest.fixture() def agent(): with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs("web_search")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", @@ -229,7 +229,7 @@ def _run_marathon_turn( patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), patch( - "run_agent.handle_function_call", + "model_tools.handle_function_call", lambda name, args, task_id=None, **kwargs: json.dumps( {"ok": True, "payload": BIG_TOOL_RESULT} ), diff --git a/tests/run_agent/test_compression_feasibility.py b/tests/run_agent/test_compression_feasibility.py index 8626841cb6..9017ea67d9 100644 --- a/tests/run_agent/test_compression_feasibility.py +++ b/tests/run_agent/test_compression_feasibility.py @@ -231,10 +231,10 @@ def test_init_feasibility_check_uses_aux_context_override_from_config(): with ( patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), - patch("run_agent.ContextCompressor", new=_StubCompressor), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), + patch("agent.agent_init.ContextCompressor", new=_StubCompressor), patch("agent.auxiliary_client.get_text_auxiliary_client", return_value=(mock_client, "custom/big-model")), patch("agent.model_metadata.get_model_context_length", return_value=1_000_000) as mock_ctx_len, ): diff --git a/tests/run_agent/test_compression_timeout_terminal.py b/tests/run_agent/test_compression_timeout_terminal.py index a858a67f14..f0dc8056d4 100644 --- a/tests/run_agent/test_compression_timeout_terminal.py +++ b/tests/run_agent/test_compression_timeout_terminal.py @@ -27,14 +27,14 @@ def _no_compression_sleep(monkeypatch): import time as _time monkeypatch.setattr(_time, "sleep", lambda *_a, **_k: None) - monkeypatch.setattr(run_agent, "jittered_backoff", lambda *a, **k: 0.0) + monkeypatch.setattr("agent.retry_utils.jittered_backoff", lambda *a, **k: 0.0) def _make_agent(): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_context_token_tracking.py b/tests/run_agent/test_context_token_tracking.py index 4f9dac0fa3..07d416e988 100644 --- a/tests/run_agent/test_context_token_tracking.py +++ b/tests/run_agent/test_context_token_tracking.py @@ -18,11 +18,11 @@ import run_agent def _patch_bootstrap(monkeypatch): - monkeypatch.setattr(run_agent, "get_tool_definitions", lambda **kwargs: [{ + monkeypatch.setattr("model_tools.get_tool_definitions", lambda **kwargs: [{ "type": "function", "function": {"name": "t", "description": "t", "parameters": {"type": "object", "properties": {}}}, }]) - monkeypatch.setattr(run_agent, "check_toolset_requirements", lambda: {}) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) class _FakeAnthropicClient: diff --git a/tests/run_agent/test_continuation_ceiling_wedge.py b/tests/run_agent/test_continuation_ceiling_wedge.py index a1fa8c676f..11d3bee03a 100644 --- a/tests/run_agent/test_continuation_ceiling_wedge.py +++ b/tests/run_agent/test_continuation_ceiling_wedge.py @@ -20,9 +20,9 @@ from hermes_constants import PARTIAL_STREAM_STUB_ID, FINISH_REASON_LENGTH def loop_agent(): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_continuation_repetition_guard.py b/tests/run_agent/test_continuation_repetition_guard.py index 80223e21f0..bfaee6bfd4 100644 --- a/tests/run_agent/test_continuation_repetition_guard.py +++ b/tests/run_agent/test_continuation_repetition_guard.py @@ -25,9 +25,9 @@ def loop_agent(): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_conversation_fallback_state.py b/tests/run_agent/test_conversation_fallback_state.py index a0d07c18ab..a1b24342b0 100644 --- a/tests/run_agent/test_conversation_fallback_state.py +++ b/tests/run_agent/test_conversation_fallback_state.py @@ -66,9 +66,9 @@ def test_substantive_tool_only_turn_invalidates_older_housekeeping_fallback(): - Step 4 returns the nudge response as the final answer """ with ( - patch("run_agent.get_tool_definitions", return_value=_tool_defs("todo", "web_search")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_tool_defs("todo", "web_search")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", @@ -104,7 +104,7 @@ def test_substantive_tool_only_turn_invalidates_older_housekeeping_fallback(): ] with ( - patch("run_agent.handle_function_call", return_value="ok"), + patch("model_tools.handle_function_call", return_value="ok"), patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), @@ -155,9 +155,9 @@ def test_bare_tool_marker_is_not_reused_as_final_response(): - Step 3 returns the nudge response as the final answer. """ with ( - patch("run_agent.get_tool_definitions", return_value=_tool_defs("skill_manage")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_tool_defs("skill_manage")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", @@ -187,7 +187,7 @@ def test_bare_tool_marker_is_not_reused_as_final_response(): ] with ( - patch("run_agent.handle_function_call", return_value="ok"), + patch("model_tools.handle_function_call", return_value="ok"), patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), diff --git a/tests/run_agent/test_copilot_native_vision_headers.py b/tests/run_agent/test_copilot_native_vision_headers.py index 09c3258450..8e674a7b46 100644 --- a/tests/run_agent/test_copilot_native_vision_headers.py +++ b/tests/run_agent/test_copilot_native_vision_headers.py @@ -4,7 +4,7 @@ from run_agent import AIAgent def _make_copilot_agent(): - with patch("run_agent.OpenAI") as mock_openai: + with patch("agent.process_bootstrap.OpenAI") as mock_openai: mock_openai.return_value = MagicMock() agent = AIAgent( api_key="gh-token", diff --git a/tests/run_agent/test_create_openai_client_disables_sdk_retries.py b/tests/run_agent/test_create_openai_client_disables_sdk_retries.py index 77335e1748..5bfd1862b0 100644 --- a/tests/run_agent/test_create_openai_client_disables_sdk_retries.py +++ b/tests/run_agent/test_create_openai_client_disables_sdk_retries.py @@ -13,7 +13,7 @@ from unittest.mock import MagicMock, patch from run_agent import AIAgent -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_create_openai_client_disables_sdk_retries(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( @@ -46,7 +46,7 @@ def test_create_openai_client_disables_sdk_retries(mock_openai): ) -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_create_openai_client_honors_explicit_max_retries(mock_openai): """An explicit max_retries in client_kwargs is respected (setdefault, not clobber) — future callers can opt back into SDK retries if needed.""" diff --git a/tests/run_agent/test_create_openai_client_kwargs_isolation.py b/tests/run_agent/test_create_openai_client_kwargs_isolation.py index 98b7ff480c..e73ac39fbf 100644 --- a/tests/run_agent/test_create_openai_client_kwargs_isolation.py +++ b/tests/run_agent/test_create_openai_client_kwargs_isolation.py @@ -15,7 +15,7 @@ from unittest.mock import MagicMock, patch from run_agent import AIAgent -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_create_openai_client_does_not_mutate_input_kwargs(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( diff --git a/tests/run_agent/test_create_openai_client_proxy_env.py b/tests/run_agent/test_create_openai_client_proxy_env.py index 580fb19515..3d158a84f6 100644 --- a/tests/run_agent/test_create_openai_client_proxy_env.py +++ b/tests/run_agent/test_create_openai_client_proxy_env.py @@ -14,7 +14,8 @@ from unittest.mock import patch import httpx -from run_agent import AIAgent, _get_proxy_from_env, _get_proxy_for_base_url +from agent.process_bootstrap import _get_proxy_for_base_url, _get_proxy_from_env +from run_agent import AIAgent def _make_agent(): @@ -60,7 +61,7 @@ def test_get_proxy_from_env_normalizes_socks_alias(monkeypatch): assert _get_proxy_from_env() == "socks5://127.0.0.1:1080/" -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_create_openai_client_routes_via_proxy_when_env_set(mock_openai, monkeypatch): """With HTTPS_PROXY set, the custom httpx.Client must mount an HTTPProxy pool. @@ -100,7 +101,7 @@ def test_create_openai_client_routes_via_proxy_when_env_set(mock_openai, monkeyp http_client.close() -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_create_openai_client_no_proxy_when_env_unset(mock_openai, monkeypatch): """Without proxy env vars, no HTTPProxy mount should exist and no custom socket-options transport should be installed.""" diff --git a/tests/run_agent/test_create_openai_client_reuse.py b/tests/run_agent/test_create_openai_client_reuse.py index d42ceb2176..0f2a9d2eda 100644 --- a/tests/run_agent/test_create_openai_client_reuse.py +++ b/tests/run_agent/test_create_openai_client_reuse.py @@ -81,7 +81,7 @@ def test_second_create_does_not_wrap_closed_transport_from_first(): "base_url": "https://api.example.com/v1", } - with patch("run_agent.OpenAI", fake_openai): + with patch("agent.process_bootstrap.OpenAI", fake_openai): # Call 1 — what _replace_primary_openai_client does at init/rebuild. client_a = agent._create_openai_client( agent._client_kwargs, reason="initial", shared=True @@ -156,7 +156,7 @@ def test_replace_primary_openai_client_survives_repeated_rebuilds(): "base_url": "https://api.example.com/v1", } - with patch("run_agent.OpenAI", fake_openai): + with patch("agent.process_bootstrap.OpenAI", fake_openai): # Seed the initial client so _replace has something to tear down. agent.client = agent._create_openai_client( agent._client_kwargs, reason="seed", shared=True diff --git a/tests/run_agent/test_credential_pool_interrupt.py b/tests/run_agent/test_credential_pool_interrupt.py index ebeef44e12..1a14bc54cf 100644 --- a/tests/run_agent/test_credential_pool_interrupt.py +++ b/tests/run_agent/test_credential_pool_interrupt.py @@ -41,7 +41,7 @@ def test_rotate_immediately_when_credential_already_exhausted(): pool.mark_exhausted_and_rotate.return_value = entries[1] from run_agent import AIAgent - with patch("run_agent.get_tool_definitions", return_value=[]), patch("run_agent.check_toolset_requirements", return_value={}), patch("run_agent.OpenAI"): + with patch("model_tools.get_tool_definitions", return_value=[]), patch("model_tools.check_toolset_requirements", return_value={}), patch("agent.process_bootstrap.OpenAI"): agent = MagicMock(spec=AIAgent) agent._credential_pool = pool agent._swap_credential = MagicMock() @@ -67,7 +67,7 @@ def test_rotate_on_second_429_when_not_exhausted(): pool.mark_exhausted_and_rotate.return_value = entries[1] from run_agent import AIAgent - with patch("run_agent.get_tool_definitions", return_value=[]), patch("run_agent.check_toolset_requirements", return_value={}), patch("run_agent.OpenAI"): + with patch("model_tools.get_tool_definitions", return_value=[]), patch("model_tools.check_toolset_requirements", return_value={}), patch("agent.process_bootstrap.OpenAI"): agent = MagicMock(spec=AIAgent) agent._credential_pool = pool agent._swap_credential = MagicMock() diff --git a/tests/run_agent/test_custom_provider_extra_headers_client.py b/tests/run_agent/test_custom_provider_extra_headers_client.py index 9c7b229c13..933cfcb156 100644 --- a/tests/run_agent/test_custom_provider_extra_headers_client.py +++ b/tests/run_agent/test_custom_provider_extra_headers_client.py @@ -26,7 +26,7 @@ _PROXY_CONFIG = { } -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_custom_provider_extra_headers_applied_at_construction(mock_openai): mock_openai.return_value = MagicMock() with patch("hermes_cli.config.load_config", return_value=_PROXY_CONFIG): diff --git a/tests/run_agent/test_dict_tool_call_args.py b/tests/run_agent/test_dict_tool_call_args.py index ac249919fa..66fddd4b13 100644 --- a/tests/run_agent/test_dict_tool_call_args.py +++ b/tests/run_agent/test_dict_tool_call_args.py @@ -47,13 +47,13 @@ class _FakeClient: def test_tool_call_validation_accepts_dict_arguments(monkeypatch): from run_agent import AIAgent - monkeypatch.setattr("run_agent.OpenAI", lambda **kwargs: _FakeClient()) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", lambda **kwargs: _FakeClient()) monkeypatch.setattr( - "run_agent.get_tool_definitions", + "model_tools.get_tool_definitions", lambda *args, **kwargs: [{"function": {"name": "read_file"}}], ) monkeypatch.setattr( - "run_agent.handle_function_call", + "model_tools.handle_function_call", lambda name, args, task_id=None, **kwargs: json.dumps({"ok": True, "args": args}), ) diff --git a/tests/run_agent/test_dropped_tool_call_recovery.py b/tests/run_agent/test_dropped_tool_call_recovery.py index d7816a1e9c..9ebc182020 100644 --- a/tests/run_agent/test_dropped_tool_call_recovery.py +++ b/tests/run_agent/test_dropped_tool_call_recovery.py @@ -30,9 +30,9 @@ def loop_agent(): ``.chat.completions.create``.""" from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", @@ -157,7 +157,7 @@ class TestDroppedToolCallRecovery: it to the durable transcript — a resumed session must not replay the internal "issue the actual tool call now" instruction as user-authored context (#69630 review follow-up).""" - from run_agent import _is_ephemeral_scaffolding + from agent.session_persistence import _is_ephemeral_scaffolding from tests.run_agent.test_run_agent import _mock_response loop_agent.client.chat.completions.create.side_effect = [ diff --git a/tests/run_agent/test_fallback_api_mode_preservation.py b/tests/run_agent/test_fallback_api_mode_preservation.py index ccda8fac3a..f4572975ed 100644 --- a/tests/run_agent/test_fallback_api_mode_preservation.py +++ b/tests/run_agent/test_fallback_api_mode_preservation.py @@ -19,9 +19,9 @@ from run_agent import AIAgent def _make_agent(fallback_model=None): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_first_chunk_at_hook.py b/tests/run_agent/test_first_chunk_at_hook.py index 1e1da62fd7..7c93f002bf 100644 --- a/tests/run_agent/test_first_chunk_at_hook.py +++ b/tests/run_agent/test_first_chunk_at_hook.py @@ -50,11 +50,11 @@ def agent(): """Minimal AIAgent with mocked OpenAI client and tool loading.""" with ( patch( - "run_agent.get_tool_definitions", + "model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search"), ), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_gemini_native_reservation.py b/tests/run_agent/test_gemini_native_reservation.py index 556e200a61..e81e1142e7 100644 --- a/tests/run_agent/test_gemini_native_reservation.py +++ b/tests/run_agent/test_gemini_native_reservation.py @@ -29,9 +29,9 @@ CFG = {"agent": {}} def _build_agent(model, base_url, provider="", max_tokens=None, window=131072): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("hermes_cli.config.load_config", return_value=CFG), patch("hermes_cli.config.load_config_readonly", return_value=CFG), patch( diff --git a/tests/run_agent/test_image_rejection_fallback.py b/tests/run_agent/test_image_rejection_fallback.py index 32473818d2..98eacc91d2 100644 --- a/tests/run_agent/test_image_rejection_fallback.py +++ b/tests/run_agent/test_image_rejection_fallback.py @@ -6,7 +6,7 @@ verify that stripping preserves the role-alternation invariants providers require, and that the phrase detector fires on the expected error bodies. """ -from run_agent import _looks_like_image_content_rejection, _strip_images_from_messages +from agent.message_sanitization import _looks_like_image_content_rejection, _strip_images_from_messages class TestStripImagesPreservesAlternation: diff --git a/tests/run_agent/test_init_fallback_on_exhausted_pool.py b/tests/run_agent/test_init_fallback_on_exhausted_pool.py index d5b3d6582b..53897ce8d0 100644 --- a/tests/run_agent/test_init_fallback_on_exhausted_pool.py +++ b/tests/run_agent/test_init_fallback_on_exhausted_pool.py @@ -30,9 +30,9 @@ def test_init_tries_fallback_when_primary_returns_none(): return None, None # primary exhausted with patch("agent.auxiliary_client.resolve_provider_client", side_effect=fake_resolve), \ - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs()), \ - patch("run_agent.check_toolset_requirements", return_value={}), \ - patch("run_agent.OpenAI", return_value=MagicMock()): + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs()), \ + patch("model_tools.check_toolset_requirements", return_value={}), \ + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): agent = AIAgent( provider="alibaba-coding-plan", @@ -52,9 +52,9 @@ def test_init_tries_fallback_when_primary_returns_none(): def test_init_raises_when_no_fallback_configured(): """When primary returns None and no fallback is set, should raise.""" with patch("agent.auxiliary_client.resolve_provider_client", return_value=(None, None)), \ - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs()), \ - patch("run_agent.check_toolset_requirements", return_value={}), \ - patch("run_agent.OpenAI", return_value=MagicMock()): + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs()), \ + patch("model_tools.check_toolset_requirements", return_value={}), \ + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): with pytest.raises(RuntimeError, match="no API key was found"): AIAgent( @@ -88,9 +88,9 @@ def test_init_tries_fallback_when_openrouter_pool_exhausted(): return None, None # openrouter pool exhausted with patch("agent.auxiliary_client.resolve_provider_client", side_effect=fake_resolve), \ - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs()), \ - patch("run_agent.check_toolset_requirements", return_value={}), \ - patch("run_agent.OpenAI", return_value=MagicMock()): + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs()), \ + patch("model_tools.check_toolset_requirements", return_value={}), \ + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): agent = AIAgent( provider="openrouter", @@ -115,9 +115,9 @@ def test_init_openrouter_exhausted_without_chain_keeps_generic_error(): """openrouter exhausted + no usable fallback keeps the generic 'No LLM provider configured' error (not the named-provider one).""" with patch("agent.auxiliary_client.resolve_provider_client", return_value=(None, None)), \ - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs()), \ - patch("run_agent.check_toolset_requirements", return_value={}), \ - patch("run_agent.OpenAI", return_value=MagicMock()): + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs()), \ + patch("model_tools.check_toolset_requirements", return_value={}), \ + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): with pytest.raises(RuntimeError, match="No LLM provider configured"): AIAgent( diff --git a/tests/run_agent/test_interactive_interrupt.py b/tests/run_agent/test_interactive_interrupt.py index 78975a7395..970ebef6f5 100644 --- a/tests/run_agent/test_interactive_interrupt.py +++ b/tests/run_agent/test_interactive_interrupt.py @@ -23,7 +23,8 @@ log = logging.getLogger("interrupt_test") sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from unittest.mock import MagicMock, patch -from run_agent import AIAgent, IterationBudget +from agent.iteration_budget import IterationBudget +from run_agent import AIAgent from tools.interrupt import set_interrupt def make_slow_response(delay=2.0): @@ -99,7 +100,7 @@ def main() -> int: """Simulates the agent_thread in cli.py's chat() method.""" log.info("🟢 agent_thread starting") - with patch("run_agent.OpenAI") as MockOpenAI: + with patch("agent.process_bootstrap.OpenAI") as MockOpenAI: mock_client = MagicMock() mock_client.chat.completions.create = make_slow_response(delay=3.0) mock_client.close = MagicMock() diff --git a/tests/run_agent/test_interpreter_shutdown_turn_exit.py b/tests/run_agent/test_interpreter_shutdown_turn_exit.py index 8a392b13eb..75ffede76a 100644 --- a/tests/run_agent/test_interpreter_shutdown_turn_exit.py +++ b/tests/run_agent/test_interpreter_shutdown_turn_exit.py @@ -64,8 +64,8 @@ def _make_agent(monkeypatch, completions): from run_agent import AIAgent client = SimpleNamespace(chat=SimpleNamespace(completions=completions)) - monkeypatch.setattr("run_agent.OpenAI", lambda **kwargs: client) - monkeypatch.setattr("run_agent.get_tool_definitions", lambda *a, **k: []) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", lambda **kwargs: client) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda *a, **k: []) agent = AIAgent( model="test-model", diff --git a/tests/run_agent/test_invalid_context_length_warning.py b/tests/run_agent/test_invalid_context_length_warning.py index 95abfe585b..5fa6c7369f 100644 --- a/tests/run_agent/test_invalid_context_length_warning.py +++ b/tests/run_agent/test_invalid_context_length_warning.py @@ -15,9 +15,9 @@ def _build_agent(model_cfg, custom_providers=None, model=None): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("agent.model_metadata.get_model_context_length", return_value=128_000), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent diff --git a/tests/run_agent/test_iteration_budget_race.py b/tests/run_agent/test_iteration_budget_race.py index 9b75b1f4c5..1dfc54c558 100644 --- a/tests/run_agent/test_iteration_budget_race.py +++ b/tests/run_agent/test_iteration_budget_race.py @@ -14,7 +14,7 @@ def test_iteration_budget_used_is_thread_safe(): so a concurrent `consume()` could observe a partially-updated value or cause the C-level `list.append` to raise a ValueError ("list size changed"). """ - from run_agent import IterationBudget + from agent.iteration_budget import IterationBudget budget = IterationBudget(max_total=1000) num_threads = 10 @@ -52,7 +52,7 @@ def test_iteration_budget_used_is_thread_safe(): def test_iteration_budget_consume_returns_false_when_exhausted(): """consume() must return False once the budget is exhausted.""" - from run_agent import IterationBudget + from agent.iteration_budget import IterationBudget budget = IterationBudget(max_total=3) assert budget.consume() is True @@ -63,7 +63,7 @@ def test_iteration_budget_consume_returns_false_when_exhausted(): def test_iteration_budget_refund_restores_consume(): """refund() after consume() must allow one more consume().""" - from run_agent import IterationBudget + from agent.iteration_budget import IterationBudget budget = IterationBudget(max_total=2) assert budget.consume() is True @@ -75,7 +75,7 @@ def test_iteration_budget_refund_restores_consume(): def test_iteration_budget_used_reflects_consume_and_refund(): """used property must accurately reflect consume() and refund() calls.""" - from run_agent import IterationBudget + from agent.iteration_budget import IterationBudget budget = IterationBudget(max_total=10) @@ -92,7 +92,7 @@ def test_iteration_budget_used_reflects_consume_and_refund(): def test_iteration_budget_remaining(): """remaining property must equal max_total - used.""" - from run_agent import IterationBudget + from agent.iteration_budget import IterationBudget budget = IterationBudget(max_total=5) diff --git a/tests/run_agent/test_length_continuation_thinking_exhaustion.py b/tests/run_agent/test_length_continuation_thinking_exhaustion.py index 983d726e88..6aa97baa6b 100644 --- a/tests/run_agent/test_length_continuation_thinking_exhaustion.py +++ b/tests/run_agent/test_length_continuation_thinking_exhaustion.py @@ -90,9 +90,9 @@ def loop_agent(): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_malformed_tool_arguments.py b/tests/run_agent/test_malformed_tool_arguments.py index 311556dc3a..5bd8203cef 100644 --- a/tests/run_agent/test_malformed_tool_arguments.py +++ b/tests/run_agent/test_malformed_tool_arguments.py @@ -21,10 +21,10 @@ def _make_agent() -> AIAgent: } ] with ( - patch("run_agent.get_tool_definitions", return_value=tool_defs), - patch("run_agent.check_toolset_requirements", return_value={}), + patch("model_tools.get_tool_definitions", return_value=tool_defs), + patch("model_tools.check_toolset_requirements", return_value={}), patch("hermes_cli.config.load_config", return_value={}), - patch("run_agent.OpenAI"), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", @@ -78,7 +78,7 @@ def test_malformed_arguments_are_rejected_without_blocking_valid_sibling( return json.dumps({"ok": args["query"]}) with ( - patch("run_agent.handle_function_call", side_effect=fake_dispatch), + patch("model_tools.handle_function_call", side_effect=fake_dispatch), patch.object(agent, "_invoke_tool", side_effect=fake_dispatch), patch( "agent.tool_executor.maybe_persist_tool_result", diff --git a/tests/run_agent/test_memory_provider_init.py b/tests/run_agent/test_memory_provider_init.py index 05edbad509..9063efae91 100644 --- a/tests/run_agent/test_memory_provider_init.py +++ b/tests/run_agent/test_memory_provider_init.py @@ -57,9 +57,9 @@ def test_blank_memory_provider_does_not_auto_enable_honcho(): ) as from_global_config, patch("plugins.memory.load_memory_provider") as load_memory_provider, patch("agent.model_metadata.get_model_context_length", return_value=204_800), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent @@ -101,9 +101,9 @@ def test_aiagent_forwards_user_id_alt_to_memory_provider(): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("plugins.memory.load_memory_provider", return_value=provider), patch("agent.model_metadata.get_model_context_length", return_value=204_800), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent diff --git a/tests/run_agent/test_model_streaming_config.py b/tests/run_agent/test_model_streaming_config.py index 1ed5073f12..a29dafa5f3 100644 --- a/tests/run_agent/test_model_streaming_config.py +++ b/tests/run_agent/test_model_streaming_config.py @@ -37,7 +37,7 @@ def _build_agent(config): ) -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_false_seeds_disable_streaming(mock_openai): mock_openai.return_value = MagicMock() agent = _build_agent({"model": {**_BASE["model"], "streaming": False}}) @@ -45,7 +45,7 @@ def test_streaming_false_seeds_disable_streaming(mock_openai): assert agent._disable_streaming is True -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_absent_keeps_streaming_enabled(mock_openai): mock_openai.return_value = MagicMock() agent = _build_agent(_BASE) @@ -53,7 +53,7 @@ def test_streaming_absent_keeps_streaming_enabled(mock_openai): assert agent._disable_streaming is False -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_true_keeps_streaming_enabled(mock_openai): mock_openai.return_value = MagicMock() agent = _build_agent({"model": {**_BASE["model"], "streaming": True}}) @@ -61,7 +61,7 @@ def test_streaming_true_keeps_streaming_enabled(mock_openai): assert agent._disable_streaming is False -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_string_false_seeds_disable_streaming(mock_openai): """String falsy values ('false', '0') must also disable streaming — YAML users commonly quote booleans.""" @@ -71,7 +71,7 @@ def test_streaming_string_false_seeds_disable_streaming(mock_openai): assert agent._disable_streaming is True -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_zero_seeds_disable_streaming(mock_openai): mock_openai.return_value = MagicMock() agent = _build_agent({"model": {**_BASE["model"], "streaming": 0}}) @@ -79,7 +79,7 @@ def test_streaming_zero_seeds_disable_streaming(mock_openai): assert agent._disable_streaming is True -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_invalid_value_keeps_streaming_enabled(mock_openai): """Unrecognized values warn and keep the safe default (streaming on), rather than silently disabling or crashing init.""" @@ -89,7 +89,7 @@ def test_streaming_invalid_value_keeps_streaming_enabled(mock_openai): assert agent._disable_streaming is False -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_missing_model_section_keeps_streaming_enabled(mock_openai): mock_openai.return_value = MagicMock() agent = _build_agent({}) @@ -97,7 +97,7 @@ def test_missing_model_section_keeps_streaming_enabled(mock_openai): assert agent._disable_streaming is False -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_legacy_string_model_section_does_not_crash(mock_openai): """The top-level ``model`` key is a legacy string; init must not crash.""" mock_openai.return_value = MagicMock() @@ -106,7 +106,7 @@ def test_legacy_string_model_section_does_not_crash(mock_openai): assert agent._disable_streaming is False -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_false_applies_to_every_agent_built_from_config(mock_openai): """Delegate children are constructed through the same init, so any agent (parent or subagent) built under this config gets the escape hatch — @@ -121,7 +121,7 @@ def test_streaming_false_applies_to_every_agent_built_from_config(mock_openai): assert second._disable_streaming is True -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_streaming_false_read_from_real_config_file(mock_openai): """End-to-end: a real config.yaml in HERMES_HOME (sandboxed per-test by conftest) with ``model.streaming: false`` must seed the flag through the diff --git a/tests/run_agent/test_nonretryable_error_html_summary.py b/tests/run_agent/test_nonretryable_error_html_summary.py index 7980547057..ba65460048 100644 --- a/tests/run_agent/test_nonretryable_error_html_summary.py +++ b/tests/run_agent/test_nonretryable_error_html_summary.py @@ -55,9 +55,9 @@ def _make_agent() -> AIAgent: # validation ``ValueError`` *before* any API call, so the test passed # without ever touching the 403 summarization path. with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_nous_fallback_unavailable.py b/tests/run_agent/test_nous_fallback_unavailable.py index 24ffbaad60..62087c59d4 100644 --- a/tests/run_agent/test_nous_fallback_unavailable.py +++ b/tests/run_agent/test_nous_fallback_unavailable.py @@ -14,9 +14,9 @@ from run_agent import AIAgent def _make_agent(fallback_model=None): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_openai_client_lifecycle.py b/tests/run_agent/test_openai_client_lifecycle.py index dafe448430..1596f05a8c 100644 --- a/tests/run_agent/test_openai_client_lifecycle.py +++ b/tests/run_agent/test_openai_client_lifecycle.py @@ -80,7 +80,7 @@ def test_retry_after_api_connection_error_recreates_request_client(monkeypatch): first_request = FakeRequestClient(lambda **kwargs: (_ for _ in ()).throw(_connection_error())) second_request = FakeRequestClient(lambda **kwargs: {"ok": True}) factory = OpenAIFactory([first_request, second_request]) - monkeypatch.setattr(run_agent, "OpenAI", factory) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", factory) agent = _build_agent() @@ -106,7 +106,7 @@ def test_stale_non_stream_close_is_single_owner(monkeypatch): request_client = FakeRequestClient(slow_responder) factory = OpenAIFactory([request_client]) - monkeypatch.setattr(run_agent, "OpenAI", factory) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", factory) agent = _build_agent() agent._compute_non_stream_stale_timeout = lambda api_payload: 0.01 @@ -124,7 +124,7 @@ def test_closed_shared_client_is_recreated_before_request(monkeypatch): replacement_shared = FakeSharedClient(lambda **kwargs: {"replacement": True}) request_client = FakeRequestClient(lambda **kwargs: {"ok": "fresh-request-client"}) factory = OpenAIFactory([replacement_shared, request_client]) - monkeypatch.setattr(run_agent, "OpenAI", factory) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", factory) agent = _build_agent(shared_client=stale_shared) result = agent._interruptible_api_call({"model": agent.model, "messages": []}) @@ -154,7 +154,7 @@ def test_concurrent_requests_do_not_break_each_other_when_one_client_closes(monk first_client = FakeRequestClient(first_responder) second_client = FakeRequestClient(second_responder) factory = OpenAIFactory([first_client, second_client]) - monkeypatch.setattr(run_agent, "OpenAI", factory) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", factory) agent = _build_agent() results = {} @@ -197,7 +197,7 @@ def test_streaming_call_recreates_closed_shared_client_before_request(monkeypatc replacement_shared = FakeSharedClient(lambda **kwargs: {"replacement": True}) request_client = FakeRequestClient(lambda **kwargs: chunks) factory = OpenAIFactory([replacement_shared, request_client]) - monkeypatch.setattr(run_agent, "OpenAI", factory) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", factory) agent = _build_agent(shared_client=stale_shared) agent.stream_delta_callback = lambda _delta: None diff --git a/tests/run_agent/test_opencode_free_client_headers.py b/tests/run_agent/test_opencode_free_client_headers.py index 232c5bd3cf..3123abcebb 100644 --- a/tests/run_agent/test_opencode_free_client_headers.py +++ b/tests/run_agent/test_opencode_free_client_headers.py @@ -41,7 +41,7 @@ def _zen_call_headers(mock_openai): return dict(matching[-1].kwargs.get("default_headers") or {}) -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_opencode_free_blanks_authorization_header(mock_openai): """Whatever api_key value reaches the client build (placeholder, stale key, empty), the Authorization default header must be blanked so the @@ -62,7 +62,7 @@ def test_opencode_free_blanks_authorization_header(mock_openai): ) -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_opencode_free_sends_hermes_attribution(mock_openai): """Keyless requests still identify as Hermes (attribution headers match the opencode zen/go profiles).""" @@ -78,7 +78,7 @@ def test_opencode_free_sends_hermes_attribution(mock_openai): assert str(headers.get("User-Agent", "")).startswith("HermesAgent/") -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_other_providers_unaffected(mock_openai): """The opencode-free header policy must not leak to other providers.""" mock_openai.return_value = MagicMock() diff --git a/tests/run_agent/test_partial_stream_finish_reason.py b/tests/run_agent/test_partial_stream_finish_reason.py index 975cfacc54..20ca79c575 100644 --- a/tests/run_agent/test_partial_stream_finish_reason.py +++ b/tests/run_agent/test_partial_stream_finish_reason.py @@ -391,9 +391,9 @@ def loop_agent(): so we can stage a stub + continuation pair on .chat.completions.create.""" from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", @@ -674,9 +674,9 @@ class TestBuildAssistantMessageEmptyContentPad: def _agent_for_builder(self): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_per_model_threshold_init_ordering.py b/tests/run_agent/test_per_model_threshold_init_ordering.py index 92811fe6c2..347234debe 100644 --- a/tests/run_agent/test_per_model_threshold_init_ordering.py +++ b/tests/run_agent/test_per_model_threshold_init_ordering.py @@ -62,9 +62,9 @@ def test_plugin_engine_gets_model_thresholds_before_initial_update_model(): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("plugins.context_engine.load_context_engine", return_value=engine), patch("agent.model_metadata.get_model_context_length", return_value=1_000_000), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent diff --git a/tests/run_agent/test_plugin_context_engine_init.py b/tests/run_agent/test_plugin_context_engine_init.py index 797e0b406e..1c5dc291ca 100644 --- a/tests/run_agent/test_plugin_context_engine_init.py +++ b/tests/run_agent/test_plugin_context_engine_init.py @@ -48,9 +48,9 @@ def test_plugin_engine_gets_context_length_on_init(): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("plugins.context_engine.load_context_engine", return_value=engine), patch("agent.model_metadata.get_model_context_length", return_value=204_800), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent @@ -85,9 +85,9 @@ def test_active_context_engine_tools_survive_explicit_platform_toolsets(): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("plugins.context_engine.load_context_engine", return_value=engine), patch("agent.model_metadata.get_model_context_length", return_value=204_800), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent @@ -118,9 +118,9 @@ def test_plugin_engine_update_model_args(): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("plugins.context_engine.load_context_engine", return_value=engine), patch("agent.model_metadata.get_model_context_length", return_value=131_072), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent @@ -171,9 +171,9 @@ def test_codex_gpt55_autoraise_suppressed_for_plugin_engine(): patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("plugins.context_engine.load_context_engine", return_value=engine), patch("agent.model_metadata.get_model_context_length", return_value=272_000), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent @@ -196,9 +196,9 @@ def test_codex_gpt55_autoraise_still_applies_to_builtin_compressor(): with ( patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch("agent.context_compressor.get_model_context_length", return_value=272_000), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): from run_agent import AIAgent diff --git a/tests/run_agent/test_post_tool_compression_attempt_cap.py b/tests/run_agent/test_post_tool_compression_attempt_cap.py index 4814a0ac61..b70fd881b9 100644 --- a/tests/run_agent/test_post_tool_compression_attempt_cap.py +++ b/tests/run_agent/test_post_tool_compression_attempt_cap.py @@ -95,9 +95,9 @@ def _pressured_compressor() -> MagicMock: @pytest.fixture() def agent(): with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs("web_search")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", @@ -136,7 +136,7 @@ def _run_tool_loop(agent, n_tool_iterations: int): patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), patch( - "run_agent.handle_function_call", + "model_tools.handle_function_call", lambda name, args, task_id=None, **kwargs: json.dumps({"ok": True}), ), ): diff --git a/tests/run_agent/test_preflight_compression_cap_e2e.py b/tests/run_agent/test_preflight_compression_cap_e2e.py index e0228115f3..b952b86e80 100644 --- a/tests/run_agent/test_preflight_compression_cap_e2e.py +++ b/tests/run_agent/test_preflight_compression_cap_e2e.py @@ -68,9 +68,9 @@ def _make_agent(monkeypatch, tmp_path: Path, *, max_attempts) -> AIAgent: db = SessionDB(db_path=tmp_path / "state.db") with ( contextlib.redirect_stdout(io.StringIO()), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( base_url="https://openrouter.ai/api/v1", diff --git a/tests/run_agent/test_preflight_timeout_typed_result.py b/tests/run_agent/test_preflight_timeout_typed_result.py index a9a06476cb..94541a2850 100644 --- a/tests/run_agent/test_preflight_timeout_typed_result.py +++ b/tests/run_agent/test_preflight_timeout_typed_result.py @@ -19,9 +19,9 @@ def test_turn_start_preflight_timeout_returns_typed_result_not_exception(): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_primary_runtime_restore.py b/tests/run_agent/test_primary_runtime_restore.py index 9826d9f06e..0ea4b946f1 100644 --- a/tests/run_agent/test_primary_runtime_restore.py +++ b/tests/run_agent/test_primary_runtime_restore.py @@ -38,9 +38,9 @@ def _make_agent( ): """Create a minimal AIAgent with optional fallback config.""" with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs("web_search")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), # Unit tests must not probe live endpoints. The compressor resolves # context length lazily via a real network call against base_url; for # reachable hosts (the nous portal case) the endpoint's answer for the @@ -109,9 +109,9 @@ class TestPrimaryRuntimeSnapshot: def test_snapshot_includes_anthropic_state_when_applicable(self): """Anthropic-mode agents should snapshot Anthropic-specific state.""" with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs("web_search")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("agent.anthropic_adapter.build_anthropic_client", return_value=MagicMock()), ): agent = AIAgent( @@ -161,7 +161,7 @@ class TestRestorePrimaryRuntime: assert agent.provider == "openrouter" # Restore should bring back the primary - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): result = agent._restore_primary_runtime() assert result is True @@ -186,7 +186,7 @@ class TestRestorePrimaryRuntime: emitted = [] agent._emit_status = emitted.append - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True assert emitted == [ @@ -203,7 +203,7 @@ class TestRestorePrimaryRuntime: emitted = [] agent._emit_status = emitted.append - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True assert emitted == [] @@ -224,7 +224,7 @@ class TestRestorePrimaryRuntime: emitted = [] agent._emit_status = emitted.append with ( - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch.object( agent.context_compressor, "update_model", @@ -254,7 +254,7 @@ class TestRestorePrimaryRuntime: assert agent._fallback_index == 1 # consumed one entry - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): agent._restore_primary_runtime() assert agent._fallback_index == 0 # reset for next turn @@ -275,7 +275,7 @@ class TestRestorePrimaryRuntime: agent.context_compressor.context_length = 32000 agent.context_compressor.threshold_tokens = 25600 - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): agent._restore_primary_runtime() assert agent.context_compressor.context_length == original_ctx_len @@ -289,7 +289,7 @@ class TestRestorePrimaryRuntime: agent._fallback_activated = True agent._use_prompt_caching = not original_caching - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): agent._restore_primary_runtime() assert agent._use_prompt_caching == original_caching @@ -300,7 +300,7 @@ class TestRestorePrimaryRuntime: agent._fallback_activated = True agent.request_overrides = {"extra_body": {"fallback_only": True}} - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): result = agent._restore_primary_runtime() assert result is True @@ -339,7 +339,7 @@ class TestRestorePrimaryRuntime: agent._credential_pool = _Pool() agent._swap_credential = MagicMock() - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): result = agent._restore_primary_runtime() assert result is True @@ -396,7 +396,7 @@ class TestRestorePrimaryRuntime: primary_pool.provider = primary_provider primary_pool.has_available.return_value = False with ( - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch("agent.credential_pool.load_pool", return_value=primary_pool) as load_pool, ): result = agent._restore_primary_runtime() @@ -421,7 +421,7 @@ class TestRestorePrimaryRuntime: agent._credential_pool = fallback_pool with ( - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch( "agent.credential_pool.load_pool", side_effect=RuntimeError("auth store unavailable"), @@ -464,7 +464,7 @@ class TestRestorePrimaryRuntime: "agent.credential_pool.get_custom_provider_pool_key", return_value="custom:myllm", ), - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), ): result = agent._restore_primary_runtime() @@ -511,7 +511,7 @@ class TestRestorePrimaryRuntime: with ( patch("agent.credential_pool._load_config_safe", return_value=config), patch("agent.credential_pool.load_pool", return_value=primary_pool) as load_pool, - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), ): result = agent._restore_primary_runtime() @@ -542,7 +542,7 @@ class TestRestorePrimaryRuntime: with ( patch("agent.credential_pool._iter_custom_providers", return_value=configured), patch("agent.credential_pool.load_pool", return_value=None) as load_pool, - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), ): result = agent._restore_primary_runtime() @@ -570,7 +570,7 @@ class TestTryRecoverPrimaryTransport: agent = _make_agent(provider="custom") error = _make_transport_error("ReadTimeout") - with patch("run_agent.OpenAI", return_value=MagicMock()), \ + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), \ patch("time.sleep"): result = agent._try_recover_primary_transport( error, retry_count=3, max_retries=3, @@ -584,7 +584,7 @@ class TestTryRecoverPrimaryTransport: error = _make_transport_error("ReadTimeout") agent.request_overrides = {"extra_body": {"fallback_only": True}} - with patch("run_agent.OpenAI", return_value=MagicMock()), \ + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), \ patch("time.sleep"): result = agent._try_recover_primary_transport( error, retry_count=3, max_retries=3, @@ -649,7 +649,7 @@ class TestTryRecoverPrimaryTransport: agent = _make_agent(provider="custom") error = _make_transport_error("ReadTimeout") - with patch("run_agent.OpenAI", return_value=MagicMock()), \ + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), \ patch("time.sleep") as mock_sleep: agent._try_recover_primary_transport( error, retry_count=3, max_retries=3, @@ -661,7 +661,7 @@ class TestTryRecoverPrimaryTransport: agent = _make_agent(provider="custom") error = _make_transport_error("ReadTimeout") - with patch("run_agent.OpenAI", return_value=MagicMock()), \ + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), \ patch("time.sleep") as mock_sleep: agent._try_recover_primary_transport( error, retry_count=10, max_retries=3, @@ -675,7 +675,7 @@ class TestTryRecoverPrimaryTransport: agent = _make_agent(provider="custom") error = _make_transport_error("ReadTimeout") - with patch("run_agent.OpenAI", side_effect=Exception("socket error")), \ + with patch("agent.process_bootstrap.OpenAI", side_effect=Exception("socket error")), \ patch("time.sleep"): result = agent._try_recover_primary_transport( error, retry_count=3, max_retries=3, @@ -720,7 +720,7 @@ class TestRestoreInRunConversation: assert agent._fallback_index == 1 # Turn 2: restore primary - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True assert agent._fallback_activated is False @@ -757,7 +757,7 @@ class TestRateLimitCooldown: def test_cooldown_set_on_rate_limit_reason(self): """_try_activate_fallback with rate_limit reason sets _rate_limited_until.""" - from run_agent import FailoverReason + from agent.error_classifier import FailoverReason agent = _make_agent( fallback_model={"provider": "openrouter", "model": "anthropic/claude-sonnet-4"}, ) @@ -771,7 +771,7 @@ class TestRateLimitCooldown: def test_cooldown_not_set_when_already_on_fallback(self): """Chain-switching while already on fallback must not reset cooldown.""" - from run_agent import FailoverReason + from agent.error_classifier import FailoverReason agent = _make_agent( fallback_model=[ {"provider": "openrouter", "model": "model-a"}, @@ -806,7 +806,7 @@ class TestSwitchModelRequestOverridesSnapshot: from agent.agent_runtime_helpers import switch_model with ( - patch("run_agent.OpenAI", return_value=MagicMock()), + patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), patch( "agent.model_metadata.get_model_context_length", return_value=128_000, @@ -842,7 +842,7 @@ class TestSwitchModelRequestOverridesSnapshot: # A fallback activation mid-turn clobbers the live overrides… agent.request_overrides = {"extra_body": {"fallback_only": True}} error = _make_transport_error("ReadTimeout") - with patch("run_agent.OpenAI", return_value=MagicMock()), \ + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()), \ patch("time.sleep"): result = agent._try_recover_primary_transport( error, retry_count=3, max_retries=3, @@ -862,7 +862,7 @@ class TestSwitchModelRequestOverridesSnapshot: ) agent._fallback_activated = True agent.request_overrides = {"extra_body": {"fallback_only": True}} - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): result = agent._restore_primary_runtime() assert result is True assert agent.request_overrides == overrides diff --git a/tests/run_agent/test_provider_attribution_headers.py b/tests/run_agent/test_provider_attribution_headers.py index d24b28ef2c..becb63fab8 100644 --- a/tests/run_agent/test_provider_attribution_headers.py +++ b/tests/run_agent/test_provider_attribution_headers.py @@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch from run_agent import AIAgent -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_openrouter_base_url_applies_or_headers(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( @@ -28,7 +28,7 @@ def test_openrouter_base_url_applies_or_headers(mock_openai): assert headers["X-Title"] == "Hermes Agent" -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_ai_gateway_base_url_applies_attribution_headers(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( @@ -48,7 +48,7 @@ def test_ai_gateway_base_url_applies_attribution_headers(mock_openai): assert headers["User-Agent"].startswith("HermesAgent/") -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_routermint_base_url_applies_user_agent_header(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( @@ -66,7 +66,7 @@ def test_routermint_base_url_applies_user_agent_header(mock_openai): assert headers["User-Agent"].startswith("HermesAgent/") -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_nvidia_cloud_base_url_applies_billing_origin_header(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( @@ -87,7 +87,7 @@ def test_nvidia_cloud_base_url_applies_billing_origin_header(mock_openai): assert headers["X-BILLING-INVOKE-ORIGIN"] == "HermesAgent" -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_fireworks_applies_attribution_via_profile_fallback(mock_openai): """Fireworks has no host-specific branch — its attribution headers come from the profile.default_headers fallback, the path a model switch @@ -111,7 +111,7 @@ def test_fireworks_applies_attribution_via_profile_fallback(mock_openai): assert headers["User-Agent"].startswith("HermesAgent/") -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_opencode_go_applies_attribution_via_profile_fallback(mock_openai): """OpenCode (Zen/Go) attributes traffic by header like OpenRouter does. Without profile.default_headers the relay only sees the OpenAI SDK's @@ -135,7 +135,7 @@ def test_opencode_go_applies_attribution_via_profile_fallback(mock_openai): assert headers["User-Agent"].startswith("HermesAgent/") -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_opencode_zen_applies_attribution_via_profile_fallback(mock_openai): mock_openai.return_value = MagicMock() agent = AIAgent( @@ -156,7 +156,7 @@ def test_opencode_zen_applies_attribution_via_profile_fallback(mock_openai): assert headers["User-Agent"].startswith("HermesAgent/") -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_routed_client_preserves_openai_sdk_custom_headers(mock_openai): mock_openai.return_value = MagicMock() routed_client = SimpleNamespace( @@ -191,7 +191,7 @@ def test_routed_client_preserves_openai_sdk_custom_headers(mock_openai): -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_openrouter_headers_include_response_cache_when_enabled(mock_openai): """When openrouter.response_cache is True, the cache header is injected.""" mock_openai.return_value = MagicMock() @@ -222,7 +222,7 @@ def test_openrouter_headers_include_response_cache_when_enabled(mock_openai): # --------------------------------------------------------------------------- -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_user_default_headers_override_sdk_user_agent(mock_openai): """``model.default_headers`` lets a custom endpoint swap the OpenAI SDK User-Agent that some gateways/WAFs reject (the #40033 reproduction).""" @@ -255,7 +255,7 @@ def test_user_default_headers_override_sdk_user_agent(mock_openai): -@patch("run_agent.OpenAI") +@patch("agent.process_bootstrap.OpenAI") def test_openrouter_headers_no_cache_when_disabled(mock_openai): """When openrouter.response_cache is False, no cache headers are sent.""" mock_openai.return_value = MagicMock() diff --git a/tests/run_agent/test_provider_fallback.py b/tests/run_agent/test_provider_fallback.py index cf6c4f5475..434ee306e6 100644 --- a/tests/run_agent/test_provider_fallback.py +++ b/tests/run_agent/test_provider_fallback.py @@ -17,9 +17,9 @@ from run_agent import AIAgent, _pool_may_recover_from_rate_limit def _make_agent(fallback_model=None): """Create a minimal AIAgent with optional fallback config.""" with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_provider_parity.py b/tests/run_agent/test_provider_parity.py index 105993c7c2..d27b0a2b22 100644 --- a/tests/run_agent/test_provider_parity.py +++ b/tests/run_agent/test_provider_parity.py @@ -66,9 +66,9 @@ def _reset_auxiliary_provider_state(): def _make_agent(monkeypatch, provider, api_mode="chat_completions", base_url="https://openrouter.ai/api/v1", model=None): - monkeypatch.setattr("run_agent.get_tool_definitions", lambda **kw: _tool_defs("web_search", "terminal")) - monkeypatch.setattr("run_agent.check_toolset_requirements", lambda: {}) - monkeypatch.setattr("run_agent.OpenAI", _FakeOpenAI) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda **kw: _tool_defs("web_search", "terminal")) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _FakeOpenAI) kwargs = dict( api_key="test-key", base_url=base_url, diff --git a/tests/run_agent/test_repair_tool_call_arguments.py b/tests/run_agent/test_repair_tool_call_arguments.py index 92a8ba1c2b..6b61a25abb 100644 --- a/tests/run_agent/test_repair_tool_call_arguments.py +++ b/tests/run_agent/test_repair_tool_call_arguments.py @@ -2,9 +2,7 @@ import json -from run_agent import _repair_tool_call_arguments - - +from agent.message_sanitization import _repair_tool_call_arguments class TestRepairToolCallArguments: """Verify each repair stage in the pipeline.""" diff --git a/tests/run_agent/test_reset_aware_primary_restore.py b/tests/run_agent/test_reset_aware_primary_restore.py index efc071e996..6d7599fea1 100644 --- a/tests/run_agent/test_reset_aware_primary_restore.py +++ b/tests/run_agent/test_reset_aware_primary_restore.py @@ -96,9 +96,9 @@ def _make_tool_defs(*names): def _make_agent(fallback_model=None): with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs("web_search")), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search")), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-12345678", @@ -256,7 +256,7 @@ class TestResetAwareRestoreGate: agent._credential_pool = _FakePool("custom", next_at=None) - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True assert agent._fallback_activated is False assert agent.model == original_model @@ -269,7 +269,7 @@ class TestResetAwareRestoreGate: agent._credential_pool = _FakePool("custom", next_at=time.time() - 5) - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True assert agent._fallback_activated is False @@ -281,7 +281,7 @@ class TestResetAwareRestoreGate: agent._credential_pool = _FakePool("custom", raise_on_next=True) - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True assert agent._fallback_activated is False @@ -310,7 +310,7 @@ class TestResetAwareRestoreGate: agent._credential_pool = _FakePool("custom", next_at=None) - with patch("run_agent.OpenAI", return_value=MagicMock()): + with patch("agent.process_bootstrap.OpenAI", return_value=MagicMock()): assert agent._restore_primary_runtime() is True def test_logs_wait_only_once(self, caplog): diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/run_agent/test_run_agent_codex_responses.py index 26228f0b21..ae7b03786e 100644 --- a/tests/run_agent/test_run_agent_codex_responses.py +++ b/tests/run_agent/test_run_agent_codex_responses.py @@ -18,14 +18,13 @@ def _no_codex_backoff(monkeypatch): """Short-circuit retry backoff so Codex retry tests don't block on real wall-clock waits (5s jittered_backoff base delay + tight time.sleep loop).""" import time as _time - monkeypatch.setattr(run_agent, "jittered_backoff", lambda *a, **k: 0.0) + monkeypatch.setattr("agent.retry_utils.jittered_backoff", lambda *a, **k: 0.0) monkeypatch.setattr(_time, "sleep", lambda *_a, **_k: None) def _patch_agent_bootstrap(monkeypatch): monkeypatch.setattr( - run_agent, - "get_tool_definitions", + "model_tools.get_tool_definitions", lambda **kwargs: [ { "type": "function", @@ -37,7 +36,7 @@ def _patch_agent_bootstrap(monkeypatch): } ], ) - monkeypatch.setattr(run_agent, "check_toolset_requirements", lambda: {}) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) def _build_agent(monkeypatch): @@ -512,8 +511,8 @@ def _build_xai_agent_with_slash_enum_tool(monkeypatch): } ] - monkeypatch.setattr(run_agent, "get_tool_definitions", _fake_get_tool_definitions) - monkeypatch.setattr(run_agent, "check_toolset_requirements", lambda: {}) + monkeypatch.setattr("model_tools.get_tool_definitions", _fake_get_tool_definitions) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) agent = run_agent.AIAgent( model="grok-4.3", @@ -1352,7 +1351,7 @@ def test_try_refresh_codex_client_credentials_handles_xai_oauth(monkeypatch): "hermes_cli.auth.resolve_xai_oauth_runtime_credentials", _fake_resolve, ) - monkeypatch.setattr(run_agent, "OpenAI", _fake_openai) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _fake_openai) existing = _ExistingClient() agent.client = existing @@ -1458,7 +1457,7 @@ def test_try_refresh_copilot_client_credentials_rebuilds_client(monkeypatch): "hermes_cli.copilot_auth.get_copilot_api_token", lambda _raw: ("tid=exchanged-ide-token", None), ) - monkeypatch.setattr(run_agent, "OpenAI", _fake_openai) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _fake_openai) agent.client = _ExistingClient() ok = agent._try_refresh_copilot_client_credentials() @@ -1497,7 +1496,7 @@ def test_try_refresh_copilot_client_credentials_rebuilds_even_if_token_unchanged "hermes_cli.copilot_auth.get_copilot_api_token", lambda _raw: ("tid=fresh-exchanged", None), ) - monkeypatch.setattr(run_agent, "OpenAI", _fake_openai) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _fake_openai) ok = agent._try_refresh_copilot_client_credentials() @@ -1531,7 +1530,7 @@ def test_try_refresh_copilot_client_credentials_falls_back_when_exchange_unavail lambda _raw: None, ) monkeypatch.setattr("hermes_cli.copilot_auth.get_copilot_api_token", _boom) - monkeypatch.setattr(run_agent, "OpenAI", _fake_openai) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _fake_openai) ok = agent._try_refresh_copilot_client_credentials() diff --git a/tests/run_agent/test_sanitiser_escalation.py b/tests/run_agent/test_sanitiser_escalation.py index d75be6ece2..1e49fd3e1a 100644 --- a/tests/run_agent/test_sanitiser_escalation.py +++ b/tests/run_agent/test_sanitiser_escalation.py @@ -267,9 +267,9 @@ class TestProjectionStopsReheal: from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_sequential_tool_timeout.py b/tests/run_agent/test_sequential_tool_timeout.py index df3b9fdfb3..3f0f606611 100644 --- a/tests/run_agent/test_sequential_tool_timeout.py +++ b/tests/run_agent/test_sequential_tool_timeout.py @@ -62,7 +62,7 @@ def _deterministic_worker_start(monkeypatch): def _make_agent(tmp_path: Path) -> AIAgent: with ( patch( - "run_agent.get_tool_definitions", + "model_tools.get_tool_definitions", return_value=[ { "type": "function", @@ -74,8 +74,8 @@ def _make_agent(tmp_path: Path) -> AIAgent: } ], ), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("run_agent._hermes_home", tmp_path), patch("agent.model_metadata.fetch_model_metadata", return_value={}), ): @@ -143,7 +143,7 @@ def test_sequential_tool_timeout_emits_result_and_continues(tmp_path, monkeypatc started = time.monotonic() try: with ( - patch("run_agent.handle_function_call", side_effect=_dispatch), + patch("model_tools.handle_function_call", side_effect=_dispatch), patch( "agent.tool_executor._emit_terminal_post_tool_call", side_effect=_capture_terminal_event, @@ -266,7 +266,7 @@ def test_sequential_tool_interrupt_hides_lifecycle_cancel_detail(tmp_path, monke try: with ( - patch("run_agent.handle_function_call", side_effect=_dispatch), + patch("model_tools.handle_function_call", side_effect=_dispatch), patch( "agent.tool_executor._emit_terminal_post_tool_call", side_effect=lambda *_args, **kwargs: terminal_events.append(kwargs), @@ -327,7 +327,7 @@ def test_sequential_timeout_does_not_cut_clarify_human_wait( messages: list[dict] = [] started = time.monotonic() with ( - patch("run_agent.handle_function_call", side_effect=_dispatch), + patch("model_tools.handle_function_call", side_effect=_dispatch), patch( "agent.tool_executor._emit_terminal_post_tool_call", side_effect=_capture_terminal_event, diff --git a/tests/run_agent/test_steer.py b/tests/run_agent/test_steer.py index 1dd976b71a..9d50add447 100644 --- a/tests/run_agent/test_steer.py +++ b/tests/run_agent/test_steer.py @@ -728,9 +728,9 @@ class TestLegacyHiddenPlaceholderWireSubstitution: from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_stream_stale_breaker_reset.py b/tests/run_agent/test_stream_stale_breaker_reset.py index e62b0de227..919b9ea865 100644 --- a/tests/run_agent/test_stream_stale_breaker_reset.py +++ b/tests/run_agent/test_stream_stale_breaker_reset.py @@ -60,9 +60,9 @@ def _make_fallback_agent(fallback_model): """Full-constructor agent for the fallback path, mirroring tests/run_agent/test_24996_fallback_exhaustion_cooldown.py.""" with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_streaming.py b/tests/run_agent/test_streaming.py index 7a04ab0e68..040f374e40 100644 --- a/tests/run_agent/test_streaming.py +++ b/tests/run_agent/test_streaming.py @@ -1594,8 +1594,8 @@ class TestCopilotACPStreamingDecision: must detect ACP runtimes and route to _interruptible_api_call instead. """ - @patch("run_agent.get_tool_definitions", return_value=[]) - @patch("run_agent.check_toolset_requirements", return_value={}) + @patch("model_tools.get_tool_definitions", return_value=[]) + @patch("model_tools.check_toolset_requirements", return_value={}) @patch("agent.copilot_acp_client.CopilotACPClient") def test_provider_name_triggers_non_streaming( self, mock_acp_cls, _mock_check, _mock_tools @@ -1625,8 +1625,8 @@ class TestCopilotACPStreamingDecision: response = mock_non_stream({}) mock_stream.assert_not_called() - @patch("run_agent.get_tool_definitions", return_value=[]) - @patch("run_agent.check_toolset_requirements", return_value={}) + @patch("model_tools.get_tool_definitions", return_value=[]) + @patch("model_tools.check_toolset_requirements", return_value={}) @patch("agent.copilot_acp_client.CopilotACPClient") def test_acp_base_url_triggers_non_streaming( self, mock_acp_cls, _mock_check, _mock_tools diff --git a/tests/run_agent/test_streaming_tool_call_repair.py b/tests/run_agent/test_streaming_tool_call_repair.py index 30b765cfc2..5504828ba2 100644 --- a/tests/run_agent/test_streaming_tool_call_repair.py +++ b/tests/run_agent/test_streaming_tool_call_repair.py @@ -13,9 +13,7 @@ unclosed brackets, Python None) don't kill the session. import json -from run_agent import _repair_tool_call_arguments - - +from agent.message_sanitization import _repair_tool_call_arguments class TestStreamingAssemblyRepair: """Verify that _repair_tool_call_arguments is applied to streaming tool call arguments before they're assembled into mock_tool_calls. diff --git a/tests/run_agent/test_strict_api_validation.py b/tests/run_agent/test_strict_api_validation.py index b1f1abb5ff..5eb464bb03 100644 --- a/tests/run_agent/test_strict_api_validation.py +++ b/tests/run_agent/test_strict_api_validation.py @@ -37,9 +37,9 @@ class _FakeOpenAI: def _make_agent(monkeypatch, provider, api_mode="chat_completions", base_url="https://openrouter.ai/api/v1"): - monkeypatch.setattr("run_agent.get_tool_definitions", lambda **kw: _tool_defs("web_search", "terminal")) - monkeypatch.setattr("run_agent.check_toolset_requirements", lambda: {}) - monkeypatch.setattr("run_agent.OpenAI", _FakeOpenAI) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda **kw: _tool_defs("web_search", "terminal")) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _FakeOpenAI) return AIAgent( api_key="test", base_url=base_url, diff --git a/tests/run_agent/test_switch_model_context.py b/tests/run_agent/test_switch_model_context.py index 828b1e1c78..e768a63868 100644 --- a/tests/run_agent/test_switch_model_context.py +++ b/tests/run_agent/test_switch_model_context.py @@ -48,9 +48,9 @@ def _make_direct_start_agent( ) -> AIAgent: with ( patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("agent.agent_init.ContextCompressor", new=_StubStartupCompressor), ): return AIAgent( diff --git a/tests/run_agent/test_thinking_prefill_trailing_turn.py b/tests/run_agent/test_thinking_prefill_trailing_turn.py index acba82a5d2..1472f1f577 100644 --- a/tests/run_agent/test_thinking_prefill_trailing_turn.py +++ b/tests/run_agent/test_thinking_prefill_trailing_turn.py @@ -31,9 +31,9 @@ def loop_agent(): ``test_dropped_tool_call_recovery.py``.""" from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_token_persistence_non_cli.py b/tests/run_agent/test_token_persistence_non_cli.py index e5d0884b54..4795b60303 100644 --- a/tests/run_agent/test_token_persistence_non_cli.py +++ b/tests/run_agent/test_token_persistence_non_cli.py @@ -18,9 +18,9 @@ def _mock_response(*, usage: dict, content: str = "done"): def _make_agent(session_db, *, platform: str): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_tool_arg_coercion.py b/tests/run_agent/test_tool_arg_coercion.py index 00dcb289b0..ad0d0cf372 100644 --- a/tests/run_agent/test_tool_arg_coercion.py +++ b/tests/run_agent/test_tool_arg_coercion.py @@ -8,7 +8,7 @@ against the tool's JSON Schema before dispatch. from unittest.mock import patch -from model_tools import ( +from tools.arg_coercion import ( coerce_tool_args, _coerce_value, _coerce_number, @@ -111,7 +111,7 @@ class TestCoerceToolArgs: def test_coerces_integer_arg(self): schema = self._mock_schema({"limit": {"type": "integer"}}) - with patch("model_tools.registry.get_schema", return_value=schema): + with patch("tools.arg_coercion.registry.get_schema", return_value=schema): args = {"limit": "10"} result = coerce_tool_args("test_tool", args) assert result["limit"] == 10 @@ -122,7 +122,7 @@ class TestCoerceToolArgs: def test_leaves_already_correct_types(self): schema = self._mock_schema({"limit": {"type": "integer"}}) - with patch("model_tools.registry.get_schema", return_value=schema): + with patch("tools.arg_coercion.registry.get_schema", return_value=schema): args = {"limit": 10} result = coerce_tool_args("test_tool", args) assert result["limit"] == 10 @@ -224,7 +224,7 @@ class TestCoerceToolArgsNested: def test_array_elements_as_json_strings_are_parsed(self): schema = self._array_of_objects_schema() - with patch("model_tools.registry.get_schema", return_value=schema): + with patch("tools.arg_coercion.registry.get_schema", return_value=schema): args = {"items": ['{"id": "1", "content": "x"}']} result = coerce_tool_args("test_tool", args) assert result["items"] == [{"id": "1", "content": "x"}] @@ -233,7 +233,7 @@ class TestCoerceToolArgsNested: def test_string_subfield_with_json_content_preserved(self): """A string-typed sub-field whose value looks like JSON must NOT be parsed.""" schema = self._array_of_objects_schema() - with patch("model_tools.registry.get_schema", return_value=schema): + with patch("tools.arg_coercion.registry.get_schema", return_value=schema): args = {"items": [{"id": "1", "content": '{"not": "parsed"}'}]} result = coerce_tool_args("test_tool", args) assert result["items"][0]["content"] == '{"not": "parsed"}' diff --git a/tests/run_agent/test_tool_batch_segmentation.py b/tests/run_agent/test_tool_batch_segmentation.py index 6f367878b9..ee5b6fe9c1 100644 --- a/tests/run_agent/test_tool_batch_segmentation.py +++ b/tests/run_agent/test_tool_batch_segmentation.py @@ -383,11 +383,11 @@ def _make_tool_defs(*names: str) -> list: def agent(): with ( patch( - "run_agent.get_tool_definitions", + "model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search", "terminal"), ), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): a = AIAgent( api_key="test-key-1234567890", @@ -427,7 +427,7 @@ class TestSegmentedDispatchIntegration: events.append(("end", name, kwargs["tool_call_id"])) return json.dumps({"ok": name}) - with patch("run_agent.handle_function_call", side_effect=fake_handle): + with patch("model_tools.handle_function_call", side_effect=fake_handle): agent._execute_tool_calls(msg, messages, "task-1") # One result per call, in emission order. @@ -460,7 +460,7 @@ class TestSegmentedDispatchIntegration: executed.append(kwargs["tool_call_id"]) return json.dumps({"ok": True}) - with patch("run_agent.handle_function_call", side_effect=fake_handle): + with patch("model_tools.handle_function_call", side_effect=fake_handle): agent._execute_tool_calls(msg, messages, "task-1") assert [m["tool_call_id"] for m in messages] == ["s1", "s2", "t1", "s3", "s4"] @@ -507,7 +507,7 @@ class TestSegmentedDispatchIntegration: agent._interrupt_requested = True return json.dumps({"ok": True}) - with patch("run_agent.handle_function_call", side_effect=fake_handle): + with patch("model_tools.handle_function_call", side_effect=fake_handle): agent._execute_tool_calls(msg, messages, "task-1") # Every call still gets exactly one result, in order. @@ -532,7 +532,7 @@ class TestSegmentedDispatchIntegration: return json.dumps({"ok": True}) agent.steer("focus on the tests") - with patch("run_agent.handle_function_call", side_effect=fake_handle): + with patch("model_tools.handle_function_call", side_effect=fake_handle): agent._execute_tool_calls(msg, messages, "task-1") contents = [m["content"] for m in messages] @@ -602,7 +602,7 @@ class TestSegmentedDispatchIntegration: return "small" with ( - patch("run_agent.handle_function_call", side_effect=fake_handle), + patch("model_tools.handle_function_call", side_effect=fake_handle), patch("agent.tool_executor._budget_for_agent", return_value=budget), ): agent._execute_tool_calls(msg, messages, "task-1") diff --git a/tests/run_agent/test_tool_call_guardrail_runtime.py b/tests/run_agent/test_tool_call_guardrail_runtime.py index fbc0a51460..a88cfcdf4b 100644 --- a/tests/run_agent/test_tool_call_guardrail_runtime.py +++ b/tests/run_agent/test_tool_call_guardrail_runtime.py @@ -45,11 +45,11 @@ def _make_agent( platform: str | None = None, ) -> AIAgent: with ( - patch("run_agent.get_tool_definitions", return_value=_make_tool_defs(*tool_names)), - patch("run_agent.check_toolset_requirements", return_value={}), + patch("model_tools.get_tool_definitions", return_value=_make_tool_defs(*tool_names)), + patch("model_tools.check_toolset_requirements", return_value={}), patch("hermes_cli.config.load_config", return_value=config or {}), patch("hermes_cli.config.load_config_readonly", return_value=config or {}), - patch("run_agent.OpenAI"), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", @@ -129,7 +129,7 @@ def test_default_sequential_path_warns_repeated_exact_failure_without_blocking_e msg = SimpleNamespace(content="", tool_calls=[tc]) messages = [] - with patch("run_agent.handle_function_call", return_value=json.dumps({"error": "boom"})) as mock_hfc: + with patch("model_tools.handle_function_call", return_value=json.dumps({"error": "boom"})) as mock_hfc: agent._execute_tool_calls_sequential(msg, messages, "task-1") mock_hfc.assert_called_once() @@ -155,7 +155,7 @@ def test_config_enabled_hard_stop_blocks_repeated_exact_failure_before_execution msg = SimpleNamespace(content="", tool_calls=[tc]) messages = [] - with patch("run_agent.handle_function_call", return_value="SHOULD_NOT_RUN") as mock_hfc: + with patch("model_tools.handle_function_call", return_value="SHOULD_NOT_RUN") as mock_hfc: agent._execute_tool_calls_sequential(msg, messages, "task-1") mock_hfc.assert_not_called() @@ -175,7 +175,7 @@ def test_sequential_after_call_appends_guidance_to_tool_result_without_extra_mes msg = SimpleNamespace(content="", tool_calls=[tc]) messages = [] - with patch("run_agent.handle_function_call", return_value=json.dumps({"error": "boom"})): + with patch("model_tools.handle_function_call", return_value=json.dumps({"error": "boom"})): agent._execute_tool_calls_sequential(msg, messages, "task-1") assert [m["role"] for m in messages] == ["tool"] @@ -203,7 +203,7 @@ def test_same_tool_failure_warning_tells_model_to_recover_with_tools(): msg = SimpleNamespace(content="", tool_calls=[tc]) messages = [] - with patch("run_agent.handle_function_call", return_value=json.dumps({"exit_code": 1})): + with patch("model_tools.handle_function_call", return_value=json.dumps({"exit_code": 1})): agent._execute_tool_calls_sequential(msg, messages, "task-1") content = messages[0]["content"] @@ -236,7 +236,7 @@ def test_config_enabled_hard_stop_concurrent_path_does_not_submit_blocked_calls_ executed.append((name, args, kwargs["tool_call_id"])) return json.dumps({"ok": args["query"]}) - with patch("run_agent.handle_function_call", side_effect=fake_handle): + with patch("model_tools.handle_function_call", side_effect=fake_handle): agent._execute_tool_calls_concurrent(msg, messages, "task-1") assert executed == [("web_search", allowed_args, "c-allow")] @@ -345,7 +345,7 @@ def test_relay_rewrite_is_guarded_before_dispatch_in_concurrent_path(): agent.tool_start_callback = lambda *args: starts.append(args) with ( patch("agent.relay_tools.execute", side_effect=relay_execute), - patch("run_agent.handle_function_call", return_value="SHOULD_NOT_RUN") as dispatch, + patch("model_tools.handle_function_call", return_value="SHOULD_NOT_RUN") as dispatch, ): agent._execute_tool_calls_concurrent(msg, messages, "task-1") @@ -366,7 +366,7 @@ def test_plugin_pre_tool_block_wins_without_counting_as_toolguard_block(): "hermes_cli.plugins._dispatch_pre_tool_call_hooks", return_value=("plugin policy", None), ), - patch("run_agent.handle_function_call", return_value="SHOULD_NOT_RUN") as mock_hfc, + patch("model_tools.handle_function_call", return_value="SHOULD_NOT_RUN") as mock_hfc, ): agent._execute_tool_calls_sequential(msg, messages, "task-1") @@ -390,7 +390,7 @@ def test_default_run_conversation_warns_without_guardrail_halt(): agent.client.chat.completions.create.side_effect = responses with ( - patch("run_agent.handle_function_call", return_value=json.dumps({"error": "boom"})) as mock_hfc, + patch("model_tools.handle_function_call", return_value=json.dumps({"error": "boom"})) as mock_hfc, patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), @@ -436,7 +436,7 @@ def test_guardrail_halt_emits_final_response_through_stream_delta_callback(): agent._disable_streaming = True with ( - patch("run_agent.handle_function_call", return_value=json.dumps({"error": "boom"})), + patch("model_tools.handle_function_call", return_value=json.dumps({"error": "boom"})), patch.object(agent, "_persist_session"), patch.object(agent, "_save_trajectory"), patch.object(agent, "_cleanup_task_resources"), diff --git a/tests/run_agent/test_tool_call_incremental_persistence.py b/tests/run_agent/test_tool_call_incremental_persistence.py index 24f7a4202a..34a1517cd6 100644 --- a/tests/run_agent/test_tool_call_incremental_persistence.py +++ b/tests/run_agent/test_tool_call_incremental_persistence.py @@ -13,7 +13,7 @@ results that were produced before it fired. These tests pin the contract: These exercise the REAL production dispatch surfaces: - * sequential -> ``run_agent.handle_function_call`` (tool_executor ~1256/1298) + * sequential -> ``model_tools.handle_function_call`` (tool_executor ~1256/1298) * concurrent -> ``agent._invoke_tool`` (tool_executor ~539) Mocking the genuine dispatch surface keeps the tests deterministic (no real @@ -56,11 +56,11 @@ def _make_agent(): (hermes_home / "logs").mkdir(parents=True, exist_ok=True) with ( patch( - "run_agent.get_tool_definitions", + "model_tools.get_tool_definitions", return_value=_make_tool_defs("web_search"), ), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("run_agent._hermes_home", hermes_home), patch("agent.model_metadata.fetch_model_metadata", return_value={}), ): @@ -327,7 +327,7 @@ def test_persistence_cause_resets_between_turns(): # --------------------------------------------------------------------------- # Contract 2: the SEQUENTIAL path flushes each tool result immediately, BEFORE -# the next tool dispatches. Dispatch goes through run_agent.handle_function_call +# the next tool dispatches. Dispatch goes through model_tools.handle_function_call # (the real production surface), which we mock for determinism. # --------------------------------------------------------------------------- def test_execute_tool_calls_sequential_flushes_each_tool_result_before_next_dispatch(): @@ -355,7 +355,7 @@ def test_execute_tool_calls_sequential_flushes_each_tool_result_before_next_disp agent._flush_messages_to_session_db = MagicMock(side_effect=_record_flush) with ( - patch("run_agent.handle_function_call", side_effect=_fake_dispatch) as disp, + patch("model_tools.handle_function_call", side_effect=_fake_dispatch) as disp, patch( "agent.tool_executor.maybe_persist_tool_result", side_effect=lambda **kwargs: kwargs["content"], @@ -407,7 +407,7 @@ def test_sequential_keyboard_interrupt_emits_results_for_all_calls(): agent._flush_messages_to_session_db = MagicMock() with ( - patch("run_agent.handle_function_call", side_effect=_interrupt_dispatch), + patch("model_tools.handle_function_call", side_effect=_interrupt_dispatch), patch( "agent.tool_executor.maybe_persist_tool_result", side_effect=lambda **kwargs: kwargs["content"], @@ -459,7 +459,7 @@ def test_tool_result_is_durable_before_ui_completion_on_abnormal_exit( agent.tool_complete_callback = _ui_completion assistant_message = SimpleNamespace(content="", tool_calls=[tool_call]) dispatch_patch = ( - patch("run_agent.handle_function_call", return_value="repository result") + patch("model_tools.handle_function_call", return_value="repository result") if executor_mode == "sequential" else patch.object(agent, "_invoke_tool", return_value="repository result") ) @@ -504,7 +504,7 @@ def test_failed_tool_result_persist_blocks_completion_projection(executor_mode): agent._flush_messages_to_session_db = MagicMock(return_value=False) agent.tool_complete_callback = MagicMock() dispatch_patch = ( - patch("run_agent.handle_function_call", return_value="repository result") + patch("model_tools.handle_function_call", return_value="repository result") if executor_mode == "sequential" else patch.object(agent, "_invoke_tool", return_value="repository result") ) @@ -543,7 +543,7 @@ def test_segmented_batch_stops_before_later_segment_after_persist_failure(): with ( patch.object(agent, "_invoke_tool", return_value="first result") as invoke, - patch("run_agent.handle_function_call", return_value="second result") as dispatch, + patch("model_tools.handle_function_call", return_value="second result") as dispatch, patch( "agent.tool_executor.maybe_persist_tool_result", side_effect=lambda **kwargs: kwargs["content"], diff --git a/tests/run_agent/test_tool_name_db_persistence.py b/tests/run_agent/test_tool_name_db_persistence.py index 29596c04dd..b55e00f072 100644 --- a/tests/run_agent/test_tool_name_db_persistence.py +++ b/tests/run_agent/test_tool_name_db_persistence.py @@ -11,9 +11,9 @@ from agent.tool_dispatch_helpers import make_tool_result_message def _make_agent(session_db): with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): return AIAgent( api_key="test-key", diff --git a/tests/run_agent/test_turn_completion_explainer.py b/tests/run_agent/test_turn_completion_explainer.py index 3a7f6b62e0..8bd1900d7b 100644 --- a/tests/run_agent/test_turn_completion_explainer.py +++ b/tests/run_agent/test_turn_completion_explainer.py @@ -13,7 +13,7 @@ These tests exercise: retries and verifies the explanation reaches ``final_response``. All assertions work under the mocked OpenAI SDK used elsewhere in this -suite (we patch ``run_agent.OpenAI`` and drive ``agent.client``), so they +suite (we patch ``agent.process_bootstrap.OpenAI`` and drive ``agent.client``), so they pass identically in CI and locally. """ @@ -36,10 +36,10 @@ def _mock_response(content="Hello", finish_reason="stop", tool_calls=None): def _make_agent(max_iterations: int = 10, config: dict | None = None) -> AIAgent: with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), patch("hermes_cli.config.load_config", return_value=config or {}), - patch("run_agent.OpenAI"), + patch("agent.process_bootstrap.OpenAI"), ): agent = AIAgent( api_key="test-key-1234567890", diff --git a/tests/run_agent/test_verification_continuation_budget.py b/tests/run_agent/test_verification_continuation_budget.py index cc164e04d3..1ac4b57578 100644 --- a/tests/run_agent/test_verification_continuation_budget.py +++ b/tests/run_agent/test_verification_continuation_budget.py @@ -21,9 +21,9 @@ def _response(content="composed report"): def agent(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes")) with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), ): instance = AIAgent( session_id="verify-budget-test", diff --git a/tests/run_interrupt_test.py b/tests/run_interrupt_test.py index a539c6ca9b..f877b9040b 100644 --- a/tests/run_interrupt_test.py +++ b/tests/run_interrupt_test.py @@ -12,7 +12,8 @@ import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from unittest.mock import MagicMock, patch -from run_agent import AIAgent, IterationBudget +from agent.iteration_budget import IterationBudget +from run_agent import AIAgent from tools.delegate_tool import _run_single_child from tools.interrupt import set_interrupt, is_interrupted @@ -51,7 +52,7 @@ def main() -> int: result_holder = [None] def run_delegate(): - with patch("run_agent.OpenAI") as MockOpenAI: + with patch("agent.process_bootstrap.OpenAI") as MockOpenAI: mock_client = MagicMock() def slow_create(**kwargs): diff --git a/tests/test_copilot_initiator.py b/tests/test_copilot_initiator.py index f6cafb198b..529a7a9c13 100644 --- a/tests/test_copilot_initiator.py +++ b/tests/test_copilot_initiator.py @@ -35,9 +35,9 @@ class _FakeOpenAI: def _make_agent(monkeypatch, base_url, api_mode="chat_completions"): """Create an AIAgent pointing at the given base_url.""" - monkeypatch.setattr("run_agent.get_tool_definitions", lambda **kw: _tool_defs("web_search")) - monkeypatch.setattr("run_agent.check_toolset_requirements", lambda: {}) - monkeypatch.setattr("run_agent.OpenAI", _FakeOpenAI) + monkeypatch.setattr("model_tools.get_tool_definitions", lambda **kw: _tool_defs("web_search")) + monkeypatch.setattr("model_tools.check_toolset_requirements", lambda: {}) + monkeypatch.setattr("agent.process_bootstrap.OpenAI", _FakeOpenAI) return AIAgent( api_key="test-key", base_url=base_url, diff --git a/tests/test_ollama_num_ctx.py b/tests/test_ollama_num_ctx.py index 2ad57201c2..d4f9b8607f 100644 --- a/tests/test_ollama_num_ctx.py +++ b/tests/test_ollama_num_ctx.py @@ -146,9 +146,9 @@ class TestCompressorClampsToNumCtx: def _build_agent(self, cfg, probed_ctx): import agent.context_compressor as cc_mod with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), - patch("run_agent.OpenAI"), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), + patch("agent.process_bootstrap.OpenAI"), patch("hermes_cli.config.load_config", return_value=cfg), patch("hermes_cli.config.load_config_readonly", return_value=cfg), patch( diff --git a/tests/test_toolsets.py b/tests/test_toolsets.py index 018f178bd1..944d96b742 100644 --- a/tests/test_toolsets.py +++ b/tests/test_toolsets.py @@ -6,7 +6,6 @@ from toolsets import ( TOOLSETS, get_toolset, resolve_toolset, - resolve_multiple_toolsets, get_all_toolsets, validate_toolset, create_custom_toolset, @@ -106,9 +105,9 @@ class TestResolveToolset: -class TestResolveMultipleToolsets: - def test_combines_and_deduplicates(self): - tools = resolve_multiple_toolsets(["web", "terminal"]) +class TestResolveToolsetComposition: + def test_union_over_names_combines_and_deduplicates(self): + tools = sorted({t for name in ("web", "terminal") for t in resolve_toolset(name)}) assert "web_search" in tools assert "web_extract" in tools assert "terminal" in tools diff --git a/tests/tools/test_delegate_cron_sync_fallback.py b/tests/tools/test_delegate_cron_sync_fallback.py index 705f5f9a78..9dce06883d 100644 --- a/tests/tools/test_delegate_cron_sync_fallback.py +++ b/tests/tools/test_delegate_cron_sync_fallback.py @@ -47,10 +47,10 @@ def _make_real_child(): from run_agent import AIAgent with ( - patch("run_agent.get_tool_definitions", return_value=[]), - patch("run_agent.check_toolset_requirements", return_value={}), + patch("model_tools.get_tool_definitions", return_value=[]), + patch("model_tools.check_toolset_requirements", return_value={}), patch("hermes_cli.config.load_config", return_value={}), - patch("run_agent.OpenAI"), + patch("agent.process_bootstrap.OpenAI"), ): child = AIAgent( api_key="test-key-1234567890", diff --git a/tools/file_state.py b/tools/file_state.py index 9a0f988a38..860e12ae4c 100644 --- a/tools/file_state.py +++ b/tools/file_state.py @@ -3,7 +3,7 @@ Prevents mangled edits when concurrent subagents (same process, same filesystem) touch the same file: B writes a file A already read, so A's next write would clobber B's changes. Complements the single-agent path-overlap -check in ``run_agent._should_parallelize_tool_batch``. A process-wide +check in ``agent.tool_dispatch_helpers._should_parallelize_tool_batch``. A process-wide ``FileStateRegistry`` tracks per-agent read stamps, the global last writer and a per-path lock; every method is a no-op under ``HERMES_DISABLE_FILE_STATE_GUARD=1``. """ diff --git a/tools/schema_sanitizer.py b/tools/schema_sanitizer.py index 45fe07aa66..97b5fd7e00 100644 --- a/tools/schema_sanitizer.py +++ b/tools/schema_sanitizer.py @@ -97,7 +97,7 @@ def _sanitize_single_tool(tool: dict) -> dict: if not isinstance(top.get("properties"), dict): top["properties"] = {} # The recursive pass only handles array-form ``type: [X, "null"]``; collapse anyOf unions - # here, keeping ``nullable: true`` so ``model_tools._schema_allows_null`` still coerces. + # here, keeping ``nullable: true`` so ``tools.arg_coercion._schema_allows_null`` still coerces. top = strip_nullable_unions(top, keep_nullable_hint=True) top = _strip_top_level_combinators(top, path=name) fn["parameters"] = _strip_ref_siblings(top) diff --git a/toolsets.py b/toolsets.py index 64bc8625a7..23a8ab8e2d 100644 --- a/toolsets.py +++ b/toolsets.py @@ -381,14 +381,6 @@ def resolve_toolset(name: str, visited: Set[str] = None, *, include_registry: bo return result -def resolve_multiple_toolsets(toolset_names: List[str]) -> List[str]: - """Resolve several toolsets and return the combined, deduplicated, sorted tool names.""" - all_tools = set() - for name in toolset_names: - all_tools.update(resolve_toolset(name)) - return sorted(all_tools) - - def _get_plugin_toolset_names() -> Set[str]: """Registry toolset names absent from the static TOOLSETS dict.""" return {n for n in _registry_call("get_registered_toolset_names", ()) if n not in TOOLSETS} diff --git a/tui_gateway/session_workdir.py b/tui_gateway/session_workdir.py index b2774c27ee..a18ce8bcf6 100644 --- a/tui_gateway/session_workdir.py +++ b/tui_gateway/session_workdir.py @@ -462,7 +462,7 @@ def _rewind_active_session_history( def _history_without_ephemeral_scaffolding(history: list[dict]) -> list[dict]: """Return the durable transcript shape without transient recovery rows.""" - from run_agent import _is_ephemeral_scaffolding + from agent.session_persistence import _is_ephemeral_scaffolding return [message.copy() for message in history if not _is_ephemeral_scaffolding(message)]