fix: restore RepairOrphaned middleware export and repoint model fake to deferred_model boundary

This commit is contained in:
Adam Moussa 2026-07-09 15:11:58 -04:00
parent f9cca1ec8f
commit 184bb6802f
No known key found for this signature in database
10 changed files with 38 additions and 26 deletions

View file

@ -46,8 +46,8 @@ from .server import (
from .tools.read_finding_outcomes import read_finding_outcomes from .tools.read_finding_outcomes import read_finding_outcomes
from .tools.save_review_style import save_review_style_prompt from .tools.save_review_style import save_review_style_prompt
from .utils.analyzer_skills import SKILLS_ROUTE, skill_path_for_mode from .utils.analyzer_skills import SKILLS_ROUTE, skill_path_for_mode
from .utils.github_app import get_github_app_installation_token
from .utils.deferred_model import make_model_or_defer from .utils.deferred_model import make_model_or_defer
from .utils.github_app import get_github_app_installation_token
from .utils.model import DEFAULT_LLM_REASONING, provider_model_kwargs from .utils.model import DEFAULT_LLM_REASONING, provider_model_kwargs
from .utils.sandbox_paths import aresolve_sandbox_work_dir from .utils.sandbox_paths import aresolve_sandbox_work_dir
from .utils.sandbox_state import unwrap_sandbox_backend from .utils.sandbox_state import unwrap_sandbox_backend

View file

@ -11,6 +11,7 @@ _MIDDLEWARE_MODULES = {
"PlanModeMiddleware": ".plan_mode", "PlanModeMiddleware": ".plan_mode",
"refresh_github_proxy_before_model": ".refresh_github_proxy", "refresh_github_proxy_before_model": ".refresh_github_proxy",
"SlackAssistantStatusMiddleware": ".refresh_slack_status", "SlackAssistantStatusMiddleware": ".refresh_slack_status",
"RepairOrphanedToolCallsMiddleware": ".repair_orphaned_tool_calls",
"SandboxCircuitBreakerMiddleware": ".sandbox_circuit_breaker", "SandboxCircuitBreakerMiddleware": ".sandbox_circuit_breaker",
"SanitizeThinkingBlocksMiddleware": ".sanitize_thinking_blocks", "SanitizeThinkingBlocksMiddleware": ".sanitize_thinking_blocks",
"SanitizeToolInputsMiddleware": ".sanitize_tool_inputs", "SanitizeToolInputsMiddleware": ".sanitize_tool_inputs",
@ -37,6 +38,7 @@ __all__ = [
"WorkflowPushGuardMiddleware", "WorkflowPushGuardMiddleware",
"SandboxCircuitBreakerMiddleware", "SandboxCircuitBreakerMiddleware",
"SlackAssistantStatusMiddleware", "SlackAssistantStatusMiddleware",
"RepairOrphanedToolCallsMiddleware",
"check_message_queue_before_model", "check_message_queue_before_model",
"ensure_no_empty_msg", "ensure_no_empty_msg",
"notify_step_limit_reached", "notify_step_limit_reached",
@ -55,6 +57,7 @@ if TYPE_CHECKING:
from .plan_mode import PlanModeMiddleware from .plan_mode import PlanModeMiddleware
from .refresh_github_proxy import refresh_github_proxy_before_model from .refresh_github_proxy import refresh_github_proxy_before_model
from .refresh_slack_status import SlackAssistantStatusMiddleware from .refresh_slack_status import SlackAssistantStatusMiddleware
from .repair_orphaned_tool_calls import RepairOrphanedToolCallsMiddleware
from .sandbox_circuit_breaker import SandboxCircuitBreakerMiddleware from .sandbox_circuit_breaker import SandboxCircuitBreakerMiddleware
from .sanitize_thinking_blocks import SanitizeThinkingBlocksMiddleware from .sanitize_thinking_blocks import SanitizeThinkingBlocksMiddleware
from .sanitize_tool_inputs import SanitizeToolInputsMiddleware from .sanitize_tool_inputs import SanitizeToolInputsMiddleware

View file

@ -83,9 +83,9 @@ from .tools import (
) )
from .utils.agents_md import fetch_agents_md from .utils.agents_md import fetch_agents_md
from .utils.api_standards_skill import fetch_api_standards_skill from .utils.api_standards_skill import fetch_api_standards_skill
from .utils.deferred_model import make_model_or_defer
from .utils.github_app import get_github_app_installation_token_with_expiry from .utils.github_app import get_github_app_installation_token_with_expiry
from .utils.github_token import cache_github_token_for_thread from .utils.github_token import cache_github_token_for_thread
from .utils.deferred_model import make_model_or_defer
from .utils.model import DEFAULT_LLM_REASONING, provider_model_kwargs from .utils.model import DEFAULT_LLM_REASONING, provider_model_kwargs
from .utils.repo_prep import materialize_trusted_skills, prepare_review_repo from .utils.repo_prep import materialize_trusted_skills, prepare_review_repo
from .utils.sandbox_paths import aresolve_sandbox_work_dir from .utils.sandbox_paths import aresolve_sandbox_work_dir

View file

@ -29,7 +29,6 @@ def apply() -> None:
import importlib import importlib
from agent import server
from agent.utils import auth, authorship from agent.utils import auth, authorship
from agent.utils import slack as slack_utils from agent.utils import slack as slack_utils
@ -52,7 +51,11 @@ def apply() -> None:
def _fake_make_model(model_id: str, **kwargs: object): # noqa: ARG001 def _fake_make_model(model_id: str, **kwargs: object): # noqa: ARG001
return FakeScriptedChatModel(script=build_script()) return FakeScriptedChatModel(script=build_script())
server.make_model = _fake_make_model # ``make_model_or_defer`` (used by server/reviewer/analyzer) resolves
# ``make_model`` via the module global at call time, so patch it there.
import agent.utils.deferred_model as deferred_model
deferred_model.make_model = _fake_make_model
async def _dummy_install_token_with_expiry() -> tuple[str, str | None]: async def _dummy_install_token_with_expiry() -> tuple[str, str | None]:
return "dummy-installation-token", None return "dummy-installation-token", None

View file

@ -65,7 +65,7 @@ async def _capture_create_deep_agent_kwargs() -> dict[str, object]:
), ),
patch("agent.server.load_profile", new_callable=AsyncMock, return_value=None), patch("agent.server.load_profile", new_callable=AsyncMock, return_value=None),
patch("agent.server.fallback_model_id_for", return_value=None), patch("agent.server.fallback_model_id_for", return_value=None),
patch("agent.server.make_model", side_effect=[MagicMock(), MagicMock()]), patch("agent.utils.deferred_model.make_model", side_effect=[MagicMock(), MagicMock()]),
patch("agent.server.construct_system_prompt", return_value="prompt"), patch("agent.server.construct_system_prompt", return_value="prompt"),
patch("agent.server.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.server.create_deep_agent", side_effect=fake_create_deep_agent),
): ):

View file

@ -66,7 +66,9 @@ async def test_agent_uses_profile_subagent_model_override() -> None:
}, },
), ),
patch("agent.server.fallback_model_id_for", return_value=None), patch("agent.server.fallback_model_id_for", return_value=None),
patch("agent.server.make_model", side_effect=[main_model, subagent_model]) as make_model, patch(
"agent.utils.deferred_model.make_model", side_effect=[main_model, subagent_model]
) as make_model,
patch("agent.server.construct_system_prompt", return_value="prompt"), patch("agent.server.construct_system_prompt", return_value="prompt"),
patch("agent.server.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.server.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
@ -142,7 +144,9 @@ async def test_agent_subagent_inherits_profile_model_override_without_explicit_p
}, },
), ),
patch("agent.server.fallback_model_id_for", return_value=None), patch("agent.server.fallback_model_id_for", return_value=None),
patch("agent.server.make_model", side_effect=[main_model, subagent_model]) as make_model, patch(
"agent.utils.deferred_model.make_model", side_effect=[main_model, subagent_model]
) as make_model,
patch("agent.server.construct_system_prompt", return_value="prompt"), patch("agent.server.construct_system_prompt", return_value="prompt"),
patch("agent.server.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.server.create_deep_agent", side_effect=fake_create_deep_agent),
): ):

View file

@ -154,7 +154,7 @@ async def test_get_agent_passes_corridor_prompt_state() -> None:
return_value=(("openai:gpt-5.5", "medium"), ("openai:gpt-5.5", "low")), return_value=(("openai:gpt-5.5", "medium"), ("openai:gpt-5.5", "low")),
), ),
patch.object(server, "fallback_model_id_for", return_value=None), patch.object(server, "fallback_model_id_for", return_value=None),
patch.object(server, "make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch.object( patch.object(
server, "_load_observability_tools", new_callable=AsyncMock, return_value=[] server, "_load_observability_tools", new_callable=AsyncMock, return_value=[]
), ),

View file

@ -105,7 +105,9 @@ async def test_create_durable_run_no_webhook_when_none(monkeypatch: pytest.Monke
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_dispatch_agent_run_preserves_multitask_strategy(monkeypatch: pytest.MonkeyPatch) -> None: async def test_dispatch_agent_run_preserves_multitask_strategy(
monkeypatch: pytest.MonkeyPatch,
) -> None:
client = _FakeClient() client = _FakeClient()
monkeypatch.setattr(dispatch, "COMPLETION_WEBHOOK_URL", None) monkeypatch.setattr(dispatch, "COMPLETION_WEBHOOK_URL", None)

View file

@ -333,7 +333,7 @@ class TestRefreshProxyOnSandboxReuse:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=mock_sandbox, return_value=mock_sandbox,
), ),
patch("agent.server.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.server.construct_system_prompt", return_value="prompt"), patch("agent.server.construct_system_prompt", return_value="prompt"),
patch("agent.server.create_deep_agent", return_value=_DummyAgent()), patch("agent.server.create_deep_agent", return_value=_DummyAgent()),
patch.dict( patch.dict(
@ -386,7 +386,7 @@ class TestRefreshProxyOnSandboxReuse:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="/workspace", return_value="/workspace",
), ),
patch("agent.server.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.server.construct_system_prompt", return_value="prompt"), patch("agent.server.construct_system_prompt", return_value="prompt"),
patch("agent.server.create_deep_agent", return_value=_DummyAgent()), patch("agent.server.create_deep_agent", return_value=_DummyAgent()),
patch.dict("agent.server.SANDBOX_BACKENDS", {}, clear=True), patch.dict("agent.server.SANDBOX_BACKENDS", {}, clear=True),

View file

@ -204,7 +204,7 @@ async def test_reviewer_resolves_app_installation_token_at_run_start() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="/workspace", return_value="/workspace",
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", return_value=dummy_agent) as create_agent, patch("agent.reviewer.create_deep_agent", return_value=dummy_agent) as create_agent,
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -260,7 +260,7 @@ async def test_reviewer_reuses_app_token_for_sandbox_proxy() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
), ),
patch("agent.reviewer.fetch_agents_md", new_callable=AsyncMock, return_value=None), patch("agent.reviewer.fetch_agents_md", new_callable=AsyncMock, return_value=None),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", return_value=_DummyAgent()), patch("agent.reviewer.create_deep_agent", return_value=_DummyAgent()),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -335,7 +335,7 @@ async def test_reviewer_applies_eval_model_and_effort_overrides() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="/workspace", return_value="/workspace",
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()) as make_model, patch("agent.utils.deferred_model.make_model", return_value=MagicMock()) as make_model,
patch("agent.reviewer.create_deep_agent", return_value=dummy_agent), patch("agent.reviewer.create_deep_agent", return_value=dummy_agent),
patch( patch(
"agent.reviewer.fetch_agents_md", "agent.reviewer.fetch_agents_md",
@ -383,7 +383,7 @@ async def test_reviewer_subagent_inherits_eval_model_without_explicit_override()
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="/workspace", return_value="/workspace",
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()) as make_model, patch("agent.utils.deferred_model.make_model", return_value=MagicMock()) as make_model,
patch("agent.reviewer.create_deep_agent", return_value=dummy_agent), patch("agent.reviewer.create_deep_agent", return_value=dummy_agent),
patch( patch(
"agent.reviewer.fetch_agents_md", "agent.reviewer.fetch_agents_md",
@ -441,7 +441,7 @@ async def test_reviewer_injects_repo_style_during_eval() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="Flag table rerender regressions.", return_value="Flag table rerender regressions.",
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
patch( patch(
"agent.reviewer.fetch_agents_md", "agent.reviewer.fetch_agents_md",
@ -507,7 +507,7 @@ async def test_reviewer_inlines_org_guidelines_into_system_prompt() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=None, return_value=None,
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -572,7 +572,7 @@ async def test_reviewer_inlines_agents_md_into_system_prompt() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="Always use the design system IconButton.", return_value="Always use the design system IconButton.",
) as mock_fetch_agents_md, ) as mock_fetch_agents_md,
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -624,7 +624,7 @@ async def test_reviewer_inlines_claude_md_when_agents_md_absent() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value="# CLAUDE.md\nUse semantic tokens only.", return_value="# CLAUDE.md\nUse semantic tokens only.",
) as mock_fetch_agents_md, ) as mock_fetch_agents_md,
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -989,7 +989,7 @@ async def test_reviewer_injects_pr_review_threads_into_first_review_context() ->
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=fake_threads, return_value=fake_threads,
) as mock_fetch_threads, ) as mock_fetch_threads,
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -1065,7 +1065,7 @@ async def test_reviewer_injects_pr_review_threads_into_re_review_context() -> No
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=[], return_value=[],
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -1123,7 +1123,7 @@ async def test_reviewer_omits_threads_block_when_fetch_returns_empty() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=[], return_value=[],
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -1182,7 +1182,7 @@ async def test_reviewer_continues_when_thread_fetch_raises() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
side_effect=RuntimeError("network down"), side_effect=RuntimeError("network down"),
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -1254,7 +1254,7 @@ async def test_reviewer_populates_diff_line_set_from_github_api() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=pr_diff, return_value=pr_diff,
) as mock_fetch_diff, ) as mock_fetch_diff,
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -1319,7 +1319,7 @@ async def test_reviewer_leaves_validation_disabled_when_diff_fetch_fails() -> No
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=None, return_value=None,
), ),
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)
@ -1385,7 +1385,7 @@ async def test_reviewer_injects_pr_title_and_body_into_context() -> None:
new_callable=AsyncMock, new_callable=AsyncMock,
return_value=("Add retry logic for uploads", "Retries flaky uploads up to 3 times."), return_value=("Add retry logic for uploads", "Retries flaky uploads up to 3 times."),
) as mock_fetch_metadata, ) as mock_fetch_metadata,
patch("agent.reviewer.make_model", return_value=MagicMock()), patch("agent.utils.deferred_model.make_model", return_value=MagicMock()),
patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent), patch("agent.reviewer.create_deep_agent", side_effect=fake_create_deep_agent),
): ):
await reviewer.get_reviewer_agent(config) await reviewer.get_reviewer_agent(config)