From ea31d9077c1109f94fde5b97cd92e7920f5a3d18 Mon Sep 17 00:00:00 2001 From: jtuki Date: Sun, 5 Apr 2026 23:23:07 +0800 Subject: [PATCH] feat(gateway): add Feishu websocket ping timing overrides Allow Feishu websocket keepalive timing to be configured via platform extra config so disconnects can be detected faster in unstable networks. New optional extra settings: - ws_ping_interval - ws_ping_timeout These values are applied only when explicitly configured. Invalid values fall back to the websocket library defaults by leaving the options unset. This complements the reconnect timing settings added previously and helps reduce total recovery time after network interruptions. Co-Authored-By: Claude Opus 4.6 (1M context) --- gateway/platforms/feishu.py | 26 ++++++++++++++++++++++++++ tests/gateway/test_feishu.py | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index 304839323..7fa6e3a9d 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -272,6 +272,8 @@ class FeishuAdapterSettings: webhook_path: str ws_reconnect_nonce: int = 30 ws_reconnect_interval: int = 120 + ws_ping_interval: Optional[int] = None + ws_ping_timeout: Optional[int] = None @dataclass @@ -376,6 +378,14 @@ def _coerce_positive_int(value: Any, default: int) -> int: return parsed if parsed >= 1 else default +def _coerce_optional_positive_int(value: Any) -> Optional[int]: + try: + parsed = int(value) + except (TypeError, ValueError): + return None + return parsed if parsed >= 1 else None + + # --------------------------------------------------------------------------- # Post payload builders and parsers # --------------------------------------------------------------------------- @@ -939,11 +949,23 @@ def _run_official_feishu_ws_client(ws_client: Any, adapter: Any) -> None: asyncio.set_event_loop(loop) ws_client_module.loop = loop adapter._ws_thread_loop = loop + + original_connect = ws_client_module.websockets.connect + + async def _connect_with_overrides(*args: Any, **kwargs: Any) -> Any: + if adapter._ws_ping_interval is not None and "ping_interval" not in kwargs: + kwargs["ping_interval"] = adapter._ws_ping_interval + if adapter._ws_ping_timeout is not None and "ping_timeout" not in kwargs: + kwargs["ping_timeout"] = adapter._ws_ping_timeout + return await original_connect(*args, **kwargs) + + ws_client_module.websockets.connect = _connect_with_overrides try: ws_client.start() except Exception: pass finally: + ws_client_module.websockets.connect = original_connect pending = [t for t in asyncio.all_tasks(loop) if not t.done()] for task in pending: task.cancel() @@ -1060,6 +1082,8 @@ class FeishuAdapter(BasePlatformAdapter): ), ws_reconnect_nonce=_coerce_non_negative_int(extra.get("ws_reconnect_nonce"), 30), ws_reconnect_interval=_coerce_positive_int(extra.get("ws_reconnect_interval"), 120), + ws_ping_interval=_coerce_optional_positive_int(extra.get("ws_ping_interval")), + ws_ping_timeout=_coerce_optional_positive_int(extra.get("ws_ping_timeout")), ) def _apply_settings(self, settings: FeishuAdapterSettings) -> None: @@ -1084,6 +1108,8 @@ class FeishuAdapter(BasePlatformAdapter): self._webhook_path = settings.webhook_path self._ws_reconnect_nonce = settings.ws_reconnect_nonce self._ws_reconnect_interval = settings.ws_reconnect_interval + self._ws_ping_interval = settings.ws_ping_interval + self._ws_ping_timeout = settings.ws_ping_timeout def _build_event_handler(self) -> Any: if EventDispatcherHandler is None: diff --git a/tests/gateway/test_feishu.py b/tests/gateway/test_feishu.py index db532ba79..64ee36979 100644 --- a/tests/gateway/test_feishu.py +++ b/tests/gateway/test_feishu.py @@ -587,6 +587,32 @@ class TestAdapterModule(unittest.TestCase): self.assertEqual(settings.ws_reconnect_nonce, 0) self.assertEqual(settings.ws_reconnect_interval, 3) + def test_load_settings_accepts_custom_ws_ping_values(self): + from gateway.platforms.feishu import FeishuAdapter + + settings = FeishuAdapter._load_settings( + { + "ws_ping_interval": 10, + "ws_ping_timeout": 8, + } + ) + + self.assertEqual(settings.ws_ping_interval, 10) + self.assertEqual(settings.ws_ping_timeout, 8) + + def test_load_settings_ignores_invalid_ws_ping_values(self): + from gateway.platforms.feishu import FeishuAdapter + + settings = FeishuAdapter._load_settings( + { + "ws_ping_interval": 0, + "ws_ping_timeout": -1, + } + ) + + self.assertIsNone(settings.ws_ping_interval) + self.assertIsNone(settings.ws_ping_timeout) + class TestAdapterBehavior(unittest.TestCase): @patch.dict(os.environ, {}, clear=True)