新增Slack适配器全套核心模块,包括消息处理流水线、会话管理、配置适配、权限控制等完整功能: 1. 新增语音、视觉相关的TTS和图像分析导出接口 2. 实现消息预处理、路由、线程上下文处理的完整流水线 3. 新增账号管理、缓存机制、房间上下文提取功能 4. 支持Webhook和Socket Mode两种事件接收方式 5. 实现权限白名单、审批配对、自动状态管理功能 6. 新增配置迁移、作用域校验、重连策略等辅助模块
102 lines
3.0 KiB
Python
102 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import random
|
|
import time
|
|
from dataclasses import dataclass
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
@dataclass
|
|
class ReconnectPolicy:
|
|
base_delay_ms: float = 1000.0
|
|
max_delay_ms: float = 300_000.0
|
|
multiplier: float = 2.0
|
|
jitter_factor: float = 0.1
|
|
max_attempts: int = 0
|
|
|
|
def calculate_delay(self, attempt: int) -> float:
|
|
delay = min(self.base_delay_ms * (self.multiplier ** (attempt - 1)), self.max_delay_ms)
|
|
jitter = delay * self.jitter_factor * random.uniform(-1, 1)
|
|
return max(delay + jitter, 100.0)
|
|
|
|
def should_retry(self, attempt: int) -> bool:
|
|
return self.max_attempts <= 0 or attempt < self.max_attempts
|
|
|
|
|
|
DEFAULT_RECONNECT_POLICY = ReconnectPolicy()
|
|
|
|
|
|
@dataclass
|
|
class ReconnectState:
|
|
attempts: int = 0
|
|
last_attempt_at: float = 0.0
|
|
next_delay_ms: float = 1000.0
|
|
|
|
def record_attempt(self, policy: ReconnectPolicy) -> float:
|
|
self.attempts += 1
|
|
self.last_attempt_at = time.monotonic()
|
|
self.next_delay_ms = policy.calculate_delay(self.attempts)
|
|
return self.next_delay_ms
|
|
|
|
def reset(self) -> None:
|
|
self.attempts = 0
|
|
self.last_attempt_at = 0.0
|
|
self.next_delay_ms = 1000.0
|
|
|
|
async def wait_and_continue(self, policy: ReconnectPolicy) -> bool:
|
|
if not policy.should_retry(self.attempts):
|
|
logger.error(f"Max reconnect attempts reached ({self.attempts})")
|
|
return False
|
|
delay_ms = self.record_attempt(policy)
|
|
logger.info(f"Reconnect attempt {self.attempts}, waiting {delay_ms:.0f}ms")
|
|
await asyncio.sleep(delay_ms / 1000.0)
|
|
return True
|
|
|
|
|
|
HTTP_401_BACKOFF_POLICY = ReconnectPolicy(
|
|
base_delay_ms=5000.0,
|
|
max_delay_ms=600_000.0,
|
|
multiplier=3.0,
|
|
jitter_factor=0.2,
|
|
max_attempts=5,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Http401BackoffState:
|
|
failures: int = 0
|
|
last_failure_at: float = 0.0
|
|
circuit_open: bool = False
|
|
circuit_open_since: float = 0.0
|
|
circuit_reset_after_s: float = 300.0
|
|
|
|
def record_401(self) -> float:
|
|
now = time.monotonic()
|
|
if self.circuit_open:
|
|
return max(0.0, self.circuit_open_since + self.circuit_reset_after_s - now) * 1000.0
|
|
|
|
self.failures += 1
|
|
self.last_failure_at = now
|
|
if self.failures >= HTTP_401_BACKOFF_POLICY.max_attempts:
|
|
self.circuit_open = True
|
|
self.circuit_open_since = now
|
|
logger.warning(
|
|
f"[401 Backoff] Circuit breaker opened after {self.failures} failures, "
|
|
f"will reset in {self.circuit_reset_after_s}s"
|
|
)
|
|
return self.circuit_reset_after_s * 1000.0
|
|
|
|
return HTTP_401_BACKOFF_POLICY.calculate_delay(self.failures)
|
|
|
|
def record_success(self) -> None:
|
|
self.failures = 0
|
|
self.circuit_open = False
|
|
|
|
def reset(self) -> None:
|
|
self.failures = 0
|
|
self.last_failure_at = 0.0
|
|
self.circuit_open = False
|
|
self.circuit_open_since = 0.0
|