refactor: sms AIOHTTP_AVAILABLE flag; ElicitationHandler call_context defaults to a no-op thunk; drop stale TYPE_CHECKING/type-ignore in two tests

Self-review follow-ups on the F821 sweep:

- plugins/platforms/sms/adapter.py: the optional-import block now sets AIOHTTP_AVAILABLE like
  the homeassistant / webhook / whatsapp_cloud adapters, and both call sites test the flag.
  Removes the `if not aiohttp is not None:` double negation left by inlining
  `_aiohttp_available()`.
- tools/mcp_tool_sampling.py: `call_context` defaults to `lambda: None` so the use site is a
  single call instead of an Optional guard; the only None caller was a test. The
  `from __future__ import annotations` was noise (`Context` is a runtime import). Comment
  names the actual cycle (mcp_tool_server_run imports this module).
- gateway/platforms/helpers.py: drop the `from __future__ import annotations` — the only
  MessageEvent annotations are attribute-target locals, which are never evaluated.
- tests/gateway/test_telegram_audio_vs_voice.py, test_video_context_note.py: module-level
  `from gateway.run import GatewayRunner` like the ~100 sibling files; the
  TYPE_CHECKING block + `# type: ignore[name-defined]` were contradicting each other.
  (tests/e2e/conftest.py and test_feishu.py keep TYPE_CHECKING deliberately: they stub
  telegram/discord before importing, and FeishuAdapter is gated on optional lark_oapi.)

Mutation check: neutralising the thunk read (`captured = None`) fails
test_captured_context_is_replayed_in_consent_call; restored → 14/14 green. ty on the three
touched production files vs origin/main: 0 new, 6 resolved.
This commit is contained in:
kshitijk4poor
2026-09-07 02:44:09 +05:30
committed by kshitij
parent ab2f4602de
commit 2f1609a86c
6 changed files with 13 additions and 26 deletions

View File

@@ -2,8 +2,6 @@
stripping, thread participation tracking, GFM table → bullets, mention-pattern
compilation, and fence-aware markdown chunking."""
from __future__ import annotations
import json
import logging
import re

View File

@@ -30,7 +30,9 @@ from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret
try:
import aiohttp
from aiohttp import web
AIOHTTP_AVAILABLE = True
except ImportError: # optional ([messaging] extra)
AIOHTTP_AVAILABLE = False
aiohttp = None # type: ignore[assignment]
web = None # type: ignore[assignment]
@@ -75,7 +77,7 @@ def _new_session(**kwargs):
def check_sms_requirements() -> bool:
"""Check if SMS adapter dependencies are available."""
return aiohttp is not None and bool(
return AIOHTTP_AVAILABLE and bool(
_get_scoped_secret("TWILIO_ACCOUNT_SID") and _get_scoped_secret("TWILIO_AUTH_TOKEN"))
@@ -292,7 +294,7 @@ def _strip_markdown_for_sms(message: str) -> str:
async def _standalone_send(pconfig, chat_id, message, *, thread_id=None, media_files=None, force_document=False):
"""Out-of-process SMS delivery via the Twilio REST API (standalone_sender_fn contract)."""
auth_token = getattr(pconfig, "api_key", None) or _get_scoped_secret("TWILIO_AUTH_TOKEN", "")
if not aiohttp is not None:
if not AIOHTTP_AVAILABLE:
return {"error": "aiohttp not installed. Run: pip install aiohttp"}
account_sid = _get_scoped_secret("TWILIO_ACCOUNT_SID", "")
from_number = os.getenv("TWILIO_PHONE_NUMBER", "")

View File

@@ -18,17 +18,11 @@ import pytest
from gateway.config import GatewayConfig, Platform
from gateway.platforms.event import MessageEvent, MessageType
from gateway.run import GatewayRunner
from gateway.session import SessionSource
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from gateway.run import GatewayRunner
def _make_runner(stt_enabled: bool = True) -> "GatewayRunner": # type: ignore[name-defined]
from gateway.run import GatewayRunner
def _make_runner(stt_enabled: bool = True) -> GatewayRunner:
runner = GatewayRunner.__new__(GatewayRunner)
runner.config = GatewayConfig(stt_enabled=stt_enabled)
runner.adapters = {}

View File

@@ -6,17 +6,11 @@ import pytest
from gateway.config import GatewayConfig, Platform
from gateway.platforms.event import MessageEvent, MessageType
from gateway.run import GatewayRunner
from gateway.session import SessionSource
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from gateway.run import GatewayRunner
def _make_runner() -> "GatewayRunner": # type: ignore[name-defined]
from gateway.run import GatewayRunner
def _make_runner() -> GatewayRunner:
runner = GatewayRunner.__new__(GatewayRunner)
runner.config = GatewayConfig()
runner.adapters = {}

View File

@@ -254,7 +254,7 @@ class TestElicitationHandlerContextBridge:
call) the handler must still invoke the consent router -- just
without the contextvar replay. Otherwise CLI/TUI sessions, which
don't set HERMES_SESSION_PLATFORM, would break."""
handler = ElicitationHandler("pay", {"timeout": 5}, call_context=None)
handler = ElicitationHandler("pay", {"timeout": 5})
params = _form_params()
with patch("tools.approval_prompt.request_elicitation_consent", return_value="accept") as m:

View File

@@ -1,8 +1,6 @@
"""MCP client-side handlers for server-initiated requests: sampling
(sampling/createMessage, text and tool-use results) and elicitation."""
from __future__ import annotations
import asyncio
import functools
import json
@@ -255,12 +253,13 @@ class ElicitationHandler:
_ANSWER_RESULTS = {"accept": ("accept", "accepted"), "cancel": ("cancel", "errors")}
def __init__(self, server_name: str, config: dict,
call_context: Optional[Callable[[], Optional[Context]]] = None):
call_context: Callable[[], Optional[Context]] = lambda: None):
self.server_name = server_name
# 5 min mirrors the gateway approval default so async surfaces (Telegram, Slack) can respond.
self.timeout = _safe_numeric(config.get("timeout", 300), 300, float)
# Returns the owning MCPServerTask's contextvars snapshot for the in-flight tool call (None
# between calls). A thunk, not the task: the task module imports this one.
# between calls). A thunk, not the task: mcp_tool_server_run imports this module, so
# MCPServerTask cannot be named here.
self._call_context = call_context
self.metrics = {"requests": 0, "accepted": 0, "declined": 0, "errors": 0}
@@ -281,7 +280,7 @@ class ElicitationHandler:
consent = functools.partial(request_elicitation_consent, message, description,
timeout_seconds=int(self.timeout), surface=f"mcp-elicitation/{self.server_name}")
captured = self._call_context() if self._call_context else None
captured = self._call_context()
return consent if captured is None else (lambda: captured.copy().run(consent))
async def __call__(self, context, params):