simplify(compat): run_agent/model_tools/toolsets/acp/providers — drop 42 re-exports/aliases, repoint 15 callers + 99 test files
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
34abf954bd); 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.
This commit is contained in:
+8
-7
@@ -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().
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
+10
-16
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
+6
-30
@@ -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.<X>")`,
|
||||
# `from run_agent import X`, or `_ra().<X>` 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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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", {})
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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=([], [], []),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from providers import ProviderProfile
|
||||
from providers.base import ProviderProfile
|
||||
import providers
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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), \
|
||||
|
||||
@@ -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),
|
||||
):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}),
|
||||
)
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}),
|
||||
),
|
||||
):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"}'
|
||||
|
||||
@@ -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")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user