from __future__ import annotations import asyncio import aiohttp from yuxi.channels.models import DeliveryResult from yuxi.utils.logging_config import logger async def send_with_retry( http_client: aiohttp.ClientSession, token: str, api_base: str, payload: dict, config: dict | None = None, ) -> DeliveryResult: cfg = config or {} retry_cfg = cfg.get("retry", {}) max_retries = retry_cfg.get("attempts", 3) min_delay = retry_cfg.get("min_delay_ms", 400) / 1000 max_delay = retry_cfg.get("max_delay_ms", 30000) / 1000 send_timeout = cfg.get("send_timeout", 30) headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json", } url = f"{api_base}/api/v1/bot/send/message" last_error = None for attempt in range(max_retries): try: async with http_client.post( url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=send_timeout), ) as resp: if resp.status == 200: data = await resp.json() return DeliveryResult( success=True, message_id=data.get("msg_id"), ) elif resp.status == 401: return DeliveryResult( success=False, error="Auth failed (401): token expired", auth_expired=True, ) elif resp.status == 429: try: retry_after = int(resp.headers.get("Retry-After", "30")) except (ValueError, TypeError): retry_after = 30 retry_after = max(retry_after, 1) logger.warning(f"[Yuanbao] Rate limited, retry after {retry_after}s") await asyncio.sleep(retry_after) continue else: error_body = await resp.text() last_error = f"Send failed ({resp.status}): {error_body}" except TimeoutError: last_error = "Request timeout" except aiohttp.ClientError as e: last_error = f"Network error: {e}" logger.debug(f"[Yuanbao] Send attempt {attempt + 1} failed: {e}") if attempt < max_retries - 1: delay = min(min_delay * (2**attempt), max_delay) await asyncio.sleep(delay) return DeliveryResult( success=False, error=str(last_error) if last_error else "Max retries exceeded", )