fix: resolve all call-non-callable ty diagnostics across codebase

Replace hasattr() duck-typing with isinstance() checks for DiscordAdapter
in gateway/run.py, add TypedDict for IMAGEGEN_BACKENDS in tools_config.py,
properly type fal_client getattr'd callables in image_generation_tool.py,
fix dict[str, object] → Callable annotation in approval.py, use
isinstance(BaseModel) in web_tools.py, capture _message_handler to local
in base.py, rename shadowed list_distributions parameter in batch_runner.py,
and remove dead queue_message branch.
This commit is contained in:
alt-glitch 2026-04-21 16:00:37 +05:30
parent 971542d254
commit 3eddabf53b
7 changed files with 61 additions and 47 deletions

View file

@ -1833,8 +1833,11 @@ class BasePlatformAdapter(ABC):
try:
await self._run_processing_hook("on_processing_start", event)
# Call the handler (this can take a while with tool calls)
response = await self._message_handler(event)
handler = self._message_handler
if handler is None:
return
response = await handler(event)
# Send response if any. A None/empty response is normal when
# streaming already delivered the text (already_sent=True) or

View file

@ -4431,9 +4431,10 @@ class GatewayRunner:
# is speaking, without needing a separate tool call.
# -----------------------------------------------------------------
if source.platform == Platform.DISCORD:
from gateway.platforms.discord import DiscordAdapter
adapter = self.adapters.get(Platform.DISCORD)
guild_id = self._get_guild_id(event)
if guild_id and adapter and hasattr(adapter, "get_voice_channel_context"):
if guild_id and isinstance(adapter, DiscordAdapter):
vc_context = adapter.get_voice_channel_context(guild_id)
if vc_context:
context_prompt += f"\n\n{vc_context}"
@ -6026,9 +6027,10 @@ class GatewayRunner:
"all": "TTS (voice reply to all messages)",
}
# Append voice channel info if connected
from gateway.platforms.discord import DiscordAdapter
adapter = self.adapters.get(event.source.platform)
guild_id = self._get_guild_id(event)
if guild_id and hasattr(adapter, "get_voice_channel_info"):
if guild_id and isinstance(adapter, DiscordAdapter):
info = adapter.get_voice_channel_info(guild_id)
if info:
lines = [
@ -6059,8 +6061,9 @@ class GatewayRunner:
async def _handle_voice_channel_join(self, event: MessageEvent) -> str:
"""Join the user's current Discord voice channel."""
from gateway.platforms.discord import DiscordAdapter
adapter = self.adapters.get(event.source.platform)
if not hasattr(adapter, "join_voice_channel"):
if not isinstance(adapter, DiscordAdapter):
return "Voice channels are not supported on this platform."
guild_id = self._get_guild_id(event)
@ -6075,10 +6078,8 @@ class GatewayRunner:
# Wire callbacks BEFORE join so voice input arriving immediately
# after connection is not lost.
if hasattr(adapter, "_voice_input_callback"):
adapter._voice_input_callback = self._handle_voice_channel_input
if hasattr(adapter, "_on_voice_disconnect"):
adapter._on_voice_disconnect = self._handle_voice_timeout_cleanup
adapter._voice_input_callback = self._handle_voice_channel_input
adapter._on_voice_disconnect = self._handle_voice_timeout_cleanup
try:
success = await adapter.join_voice_channel(voice_channel)
@ -6095,8 +6096,7 @@ class GatewayRunner:
if success:
adapter._voice_text_channels[guild_id] = int(event.source.chat_id)
if hasattr(adapter, "_voice_sources"):
adapter._voice_sources[guild_id] = event.source.to_dict()
adapter._voice_sources[guild_id] = event.source.to_dict()
self._voice_mode[self._voice_key(event.source.platform, event.source.chat_id)] = "all"
self._save_voice_modes()
self._set_adapter_auto_tts_disabled(adapter, event.source.chat_id, disabled=False)
@ -6110,13 +6110,14 @@ class GatewayRunner:
async def _handle_voice_channel_leave(self, event: MessageEvent) -> str:
"""Leave the Discord voice channel."""
from gateway.platforms.discord import DiscordAdapter
adapter = self.adapters.get(event.source.platform)
guild_id = self._get_guild_id(event)
if not guild_id or not hasattr(adapter, "leave_voice_channel"):
if not guild_id or not isinstance(adapter, DiscordAdapter):
return "Not in a voice channel."
if not hasattr(adapter, "is_in_voice_channel") or not adapter.is_in_voice_channel(guild_id):
if not adapter.is_in_voice_channel(guild_id):
return "Not in a voice channel."
try:
@ -6127,8 +6128,7 @@ class GatewayRunner:
self._voice_mode[self._voice_key(event.source.platform, event.source.chat_id)] = "off"
self._save_voice_modes()
self._set_adapter_auto_tts_disabled(adapter, event.source.chat_id, disabled=True)
if hasattr(adapter, "_voice_input_callback"):
adapter._voice_input_callback = None
adapter._voice_input_callback = None
return "Left voice channel."
def _handle_voice_timeout_cleanup(self, chat_id: str) -> None:
@ -6288,13 +6288,13 @@ class GatewayRunner:
adapter = self.adapters.get(event.source.platform)
# If connected to a voice channel, play there instead of sending a file
from gateway.platforms.discord import DiscordAdapter
guild_id = self._get_guild_id(event)
if (guild_id
and hasattr(adapter, "play_in_voice_channel")
and hasattr(adapter, "is_in_voice_channel")
and isinstance(adapter, DiscordAdapter)
and adapter.is_in_voice_channel(guild_id)):
await adapter.play_in_voice_channel(guild_id, actual_path)
elif adapter and hasattr(adapter, "send_voice"):
elif adapter:
send_kwargs: Dict[str, Any] = {
"chat_id": event.source.chat_id,
"audio_path": actual_path,
@ -10627,8 +10627,6 @@ class GatewayRunner:
adapter = self.adapters.get(source.platform)
if adapter and pending_event:
merge_pending_message_event(adapter._pending_messages, session_key, pending_event)
elif adapter and hasattr(adapter, 'queue_message'):
adapter.queue_message(session_key, pending)
return result_holder[0] or {"final_response": response, "messages": history}
was_interrupted = result.get("interrupted")

View file

@ -13,7 +13,7 @@ import json as _json
import logging
import sys
from pathlib import Path
from typing import Dict, List, Optional, Set
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TypedDict
from hermes_cli.config import (
@ -1098,13 +1098,19 @@ def _detect_active_provider_index(providers: list, config: dict) -> int:
# right catalog at picker time.
def _fal_model_catalog():
class _ImagegenBackend(TypedDict):
display: str
config_key: str
catalog_fn: Callable[[], Tuple[Dict[str, Dict[str, Any]], str]]
def _fal_model_catalog() -> Tuple[Dict[str, Dict[str, Any]], str]:
"""Lazy-load the FAL model catalog from the tool module."""
from tools.image_generation_tool import FAL_MODELS, DEFAULT_MODEL
return FAL_MODELS, DEFAULT_MODEL
IMAGEGEN_BACKENDS = {
IMAGEGEN_BACKENDS: Dict[str, _ImagegenBackend] = {
"fal": {
"display": "FAL.ai",
"config_key": "image_gen",

View file

@ -1130,7 +1130,7 @@ def main(
num_workers: int = 4,
resume: bool = False,
verbose: bool = False,
list_distributions: bool = False,
show_distributions: bool = False,
ephemeral_system_prompt: str = None,
log_prefix_chars: int = 100,
providers_allowed: str = None,
@ -1158,7 +1158,7 @@ def main(
num_workers (int): Number of parallel worker processes (default: 4)
resume (bool): Resume from checkpoint if run was interrupted (default: False)
verbose (bool): Enable verbose logging (default: False)
list_distributions (bool): List available toolset distributions and exit
show_distributions (bool): List available toolset distributions and exit
ephemeral_system_prompt (str): System prompt used during agent execution but NOT saved to trajectories (optional)
log_prefix_chars (int): Number of characters to show in log previews for tool calls/responses (default: 20)
providers_allowed (str): Comma-separated list of OpenRouter providers to allow (e.g. "anthropic,openai")
@ -1190,10 +1190,10 @@ def main(
--prefill_messages_file=configs/prefill_opus.json
# List available distributions
python batch_runner.py --list_distributions
python batch_runner.py --show_distributions
"""
# Handle list distributions
if list_distributions:
if show_distributions:
from toolset_distributions import print_distribution_info
print("📊 Available Toolset Distributions")

View file

@ -16,7 +16,7 @@ import sys
import threading
import time
import unicodedata
from typing import Optional
from typing import Any, Callable, Dict, Optional
logger = logging.getLogger(__name__)
@ -228,10 +228,10 @@ class _ApprovalEntry:
_gateway_queues: dict[str, list] = {} # session_key → [_ApprovalEntry, …]
_gateway_notify_cbs: dict[str, object] = {} # session_key → callable(approval_data)
_gateway_notify_cbs: Dict[str, Callable[[Dict[str, Any]], None]] = {}
def register_gateway_notify(session_key: str, cb) -> None:
def register_gateway_notify(session_key: str, cb: Callable[[Dict[str, Any]], None]) -> None:
"""Register a per-session callback for sending approval requests to the user.
The callback signature is ``cb(approval_data: dict) -> None`` where

View file

@ -26,10 +26,11 @@ import os
import datetime
import threading
import uuid
from typing import Any, Dict, Optional, Union
from typing import Any, Callable, Dict, Optional, Type, Union
from urllib.parse import urlencode
import fal_client
import httpx
from tools.debug_helpers import DebugSession
from tools.managed_tool_gateway import resolve_managed_tool_gateway
@ -348,21 +349,27 @@ class _ManagedFalSyncClient:
self._queue_url_format = _normalize_fal_queue_url_format(queue_run_origin)
self._sync_client = sync_client_class(key=key)
self._http_client = getattr(self._sync_client, "_client", None)
self._maybe_retry_request = getattr(client_module, "_maybe_retry_request", None)
self._raise_for_status = getattr(client_module, "_raise_for_status", None)
self._request_handle_class = getattr(client_module, "SyncRequestHandle", None)
self._add_hint_header = getattr(client_module, "add_hint_header", None)
self._add_priority_header = getattr(client_module, "add_priority_header", None)
self._add_timeout_header = getattr(client_module, "add_timeout_header", None)
if self._http_client is None:
http_client: Optional[httpx.Client] = getattr(self._sync_client, "_client", None)
maybe_retry: Optional[Callable[..., httpx.Response]] = getattr(client_module, "_maybe_retry_request", None)
raise_for_status: Optional[Callable[[httpx.Response], None]] = getattr(client_module, "_raise_for_status", None)
request_handle_class: Optional[Type[Any]] = getattr(client_module, "SyncRequestHandle", None)
if http_client is None:
raise RuntimeError("fal_client.SyncClient._client is required for managed FAL gateway mode")
if self._maybe_retry_request is None or self._raise_for_status is None:
if maybe_retry is None or raise_for_status is None:
raise RuntimeError("fal_client.client request helpers are required for managed FAL gateway mode")
if self._request_handle_class is None:
if request_handle_class is None:
raise RuntimeError("fal_client.client.SyncRequestHandle is required for managed FAL gateway mode")
self._http_client: httpx.Client = http_client
self._maybe_retry_request: Callable[..., httpx.Response] = maybe_retry
self._raise_for_status: Callable[[httpx.Response], None] = raise_for_status
self._request_handle_class: Type[Any] = request_handle_class
self._add_hint_header: Optional[Callable[..., Any]] = getattr(client_module, "add_hint_header", None)
self._add_priority_header: Optional[Callable[..., Any]] = getattr(client_module, "add_priority_header", None)
self._add_timeout_header: Optional[Callable[..., Any]] = getattr(client_module, "add_timeout_header", None)
def submit(
self,
application: str,

View file

@ -1720,8 +1720,8 @@ async def web_crawl_tool(
metadata = {}
# Extract data from the item
if hasattr(item, 'model_dump'):
# Pydantic model - use model_dump to get dict
from pydantic import BaseModel
if isinstance(item, BaseModel):
item_dict = item.model_dump()
content_markdown = item_dict.get('markdown')
content_html = item_dict.get('html')
@ -1730,15 +1730,15 @@ async def web_crawl_tool(
# Regular object with attributes
content_markdown = getattr(item, 'markdown', None)
content_html = getattr(item, 'html', None)
# Handle metadata - convert to dict if it's an object
metadata_obj = getattr(item, 'metadata', {})
if hasattr(metadata_obj, 'model_dump'):
if isinstance(metadata_obj, BaseModel):
metadata = metadata_obj.model_dump()
elif hasattr(metadata_obj, '__dict__'):
metadata = metadata_obj.__dict__
elif isinstance(metadata_obj, dict):
metadata = metadata_obj
elif hasattr(metadata_obj, '__dict__'):
metadata = metadata_obj.__dict__
else:
metadata = {}
elif isinstance(item, dict):