mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
fix: restore RepairOrphanedToolCallsMiddleware, fix E2E model-fake patch, drop dead ttl_cache
- Re-add RepairOrphanedToolCallsMiddleware to the lazy middleware __init__ (_MIDDLEWARE_MODULES, __all__, TYPE_CHECKING) so agent.reviewer can import it. - Reroute E2E model patching to deferred_model.make_model so make_model_or_defer (used by all three graph factories) returns the scripted fake instead of building a real model with fake credentials. - Drop unused agent/utils/ttl_cache.py — no agent module imports it. - Fix import ordering in agent/reviewer.py and agent/analyzer.py (ruff I001). - Format tests/test_dispatch.py.
This commit is contained in:
parent
184bb6802f
commit
65cf903583
2 changed files with 2 additions and 123 deletions
|
|
@ -10,8 +10,8 @@ _MIDDLEWARE_MODULES = {
|
||||||
"notify_step_limit_reached": ".notify_step_limit",
|
"notify_step_limit_reached": ".notify_step_limit",
|
||||||
"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",
|
|
||||||
"RepairOrphanedToolCallsMiddleware": ".repair_orphaned_tool_calls",
|
"RepairOrphanedToolCallsMiddleware": ".repair_orphaned_tool_calls",
|
||||||
|
"SlackAssistantStatusMiddleware": ".refresh_slack_status",
|
||||||
"SandboxCircuitBreakerMiddleware": ".sandbox_circuit_breaker",
|
"SandboxCircuitBreakerMiddleware": ".sandbox_circuit_breaker",
|
||||||
"SanitizeThinkingBlocksMiddleware": ".sanitize_thinking_blocks",
|
"SanitizeThinkingBlocksMiddleware": ".sanitize_thinking_blocks",
|
||||||
"SanitizeToolInputsMiddleware": ".sanitize_tool_inputs",
|
"SanitizeToolInputsMiddleware": ".sanitize_tool_inputs",
|
||||||
|
|
@ -29,6 +29,7 @@ __all__ = [
|
||||||
"ExcludeToolsMiddleware",
|
"ExcludeToolsMiddleware",
|
||||||
"ModelFallbackMiddleware",
|
"ModelFallbackMiddleware",
|
||||||
"PlanModeMiddleware",
|
"PlanModeMiddleware",
|
||||||
|
"RepairOrphanedToolCallsMiddleware",
|
||||||
"SanitizeThinkingBlocksMiddleware",
|
"SanitizeThinkingBlocksMiddleware",
|
||||||
"SanitizeToolInputsMiddleware",
|
"SanitizeToolInputsMiddleware",
|
||||||
"SubdirAgentsReadMiddleware",
|
"SubdirAgentsReadMiddleware",
|
||||||
|
|
@ -38,7 +39,6 @@ __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",
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from collections.abc import Awaitable, Callable
|
|
||||||
from typing import TypeVar, cast
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
_CACHE: dict[str, tuple[object, float]] = {}
|
|
||||||
_LOCKS: dict[tuple[str, int], asyncio.Lock] = {}
|
|
||||||
_REFRESH_TASKS: dict[tuple[str, int], asyncio.Task[None]] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def _now() -> float:
|
|
||||||
return asyncio.get_running_loop().time()
|
|
||||||
|
|
||||||
|
|
||||||
def _lock_for(key: str) -> asyncio.Lock:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
lock_key = (key, id(loop))
|
|
||||||
lock = _LOCKS.get(lock_key)
|
|
||||||
if lock is None:
|
|
||||||
lock = asyncio.Lock()
|
|
||||||
_LOCKS[lock_key] = lock
|
|
||||||
return lock
|
|
||||||
|
|
||||||
|
|
||||||
async def cached(key: str, ttl_seconds: float, loader: Callable[[], Awaitable[T]]) -> T:
|
|
||||||
now = _now()
|
|
||||||
entry = _CACHE.get(key)
|
|
||||||
if entry is not None:
|
|
||||||
value, expires_at = entry
|
|
||||||
if expires_at > now:
|
|
||||||
return cast(T, value)
|
|
||||||
|
|
||||||
async with _lock_for(key):
|
|
||||||
now = _now()
|
|
||||||
entry = _CACHE.get(key)
|
|
||||||
if entry is not None:
|
|
||||||
value, expires_at = entry
|
|
||||||
if expires_at > now:
|
|
||||||
return cast(T, value)
|
|
||||||
|
|
||||||
stale = entry[0] if entry is not None else None
|
|
||||||
has_stale = entry is not None
|
|
||||||
try:
|
|
||||||
value = await loader()
|
|
||||||
except Exception:
|
|
||||||
if has_stale:
|
|
||||||
logger.warning(
|
|
||||||
"TTL cache loader failed for %s; serving stale value", key, exc_info=True
|
|
||||||
)
|
|
||||||
return cast(T, stale)
|
|
||||||
raise
|
|
||||||
_CACHE[key] = (value, now + ttl_seconds)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
async def _refresh_stale_entry(
|
|
||||||
key: str, ttl_seconds: float, loader: Callable[[], Awaitable[object]]
|
|
||||||
) -> None:
|
|
||||||
async with _lock_for(key):
|
|
||||||
try:
|
|
||||||
value = await loader()
|
|
||||||
except Exception:
|
|
||||||
logger.warning("TTL cache background refresh failed for %s", key, exc_info=True)
|
|
||||||
return
|
|
||||||
_CACHE[key] = (value, _now() + ttl_seconds)
|
|
||||||
|
|
||||||
|
|
||||||
def _schedule_refresh(
|
|
||||||
key: str, ttl_seconds: float, loader: Callable[[], Awaitable[object]]
|
|
||||||
) -> None:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
task_key = (key, id(loop))
|
|
||||||
existing = _REFRESH_TASKS.get(task_key)
|
|
||||||
if existing is not None and not existing.done():
|
|
||||||
return
|
|
||||||
|
|
||||||
task = asyncio.create_task(_refresh_stale_entry(key, ttl_seconds, loader))
|
|
||||||
_REFRESH_TASKS[task_key] = task
|
|
||||||
task.add_done_callback(lambda _task: _REFRESH_TASKS.pop(task_key, None))
|
|
||||||
|
|
||||||
|
|
||||||
async def cached_stale_while_revalidate(
|
|
||||||
key: str,
|
|
||||||
ttl_seconds: float,
|
|
||||||
loader: Callable[[], Awaitable[T]],
|
|
||||||
) -> T:
|
|
||||||
now = _now()
|
|
||||||
entry = _CACHE.get(key)
|
|
||||||
if entry is not None:
|
|
||||||
value, expires_at = entry
|
|
||||||
if expires_at > now:
|
|
||||||
return cast(T, value)
|
|
||||||
_schedule_refresh(key, ttl_seconds, loader)
|
|
||||||
return cast(T, value)
|
|
||||||
return await cached(key, ttl_seconds, loader)
|
|
||||||
|
|
||||||
|
|
||||||
def set_cached(key: str, value: object, ttl_seconds: float) -> None:
|
|
||||||
try:
|
|
||||||
expires_at = _now() + ttl_seconds
|
|
||||||
except RuntimeError:
|
|
||||||
expires_at = float("inf")
|
|
||||||
_CACHE[key] = (value, expires_at)
|
|
||||||
|
|
||||||
|
|
||||||
def invalidate(key: str) -> None:
|
|
||||||
_CACHE.pop(key, None)
|
|
||||||
|
|
||||||
|
|
||||||
def clear() -> None:
|
|
||||||
_CACHE.clear()
|
|
||||||
_LOCKS.clear()
|
|
||||||
for task in _REFRESH_TASKS.values():
|
|
||||||
task.cancel()
|
|
||||||
_REFRESH_TASKS.clear()
|
|
||||||
Loading…
Add table
Reference in a new issue