Ctrl K
ComunidadGuías…

butler/gateway/inbound_drain.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Post-turn inbound queue drain (ENG-3 — extracted from message_handler)."""

from __future__ import annotations

import logging
import time as _time
from typing import Any, Protocol

logger = logging.getLogger(__name__)


class InboundDrainHost(Protocol):
    def handle_message(
        self,
        text: str,
        *,
        session_key: str | None = None,
        platform: str = "unknown",
        external_id: str | None = None,
    ) -> str: ...

    @property
    def _session_registry(self) -> Any: ...

    def _queue_push_via_bridge(self) -> bool: ...


def drain_queued_inbound(
    handler: InboundDrainHost,
    session_key: str,
    *,
    platform: str,
    external_id: str | None,
    primary_reply: str = "",
) -> str:
    from butler.resilience.message_queue import (
        message_queue_enabled,
        newest_enqueued_at,
        pop_all_merged,
        pop_next,
    )
    from butler.gateway.queue_settings import collect_debounce_ms, get_queue_mode

    if not message_queue_enabled():
        return ""

    mode = get_queue_mode(session_key)
    parts: list[str] = []

    if mode == "collect":
        debounce_s = collect_debounce_ms(session_key) / 1000.0
        if debounce_s > 0:
            last_ts = newest_enqueued_at(session_key)
            if last_ts > 0:
                elapsed = _time.monotonic() - last_ts
                if elapsed < debounce_s:
                    _time.sleep(debounce_s - elapsed)
        item = pop_all_merged(session_key)
        if item is not None and not handler._session_registry.is_session_active(session_key):
            logger.info(
                "Gateway drain collect session=%s preview=%r",
                session_key,
                item.text[:80],
            )
            part = handler.handle_message(
                item.text,
                session_key=session_key,
                platform=item.platform or platform,
                external_id=item.external_id or external_id,
            )
            if part:
                parts.append(part)
    else:
        try:
            from butler.utilities.env_parse import int_env
            from butler.defaults.env_defaults import GATEWAY_QUEUE_DRAIN_PER_TURN

            max_drain = int_env("BUTLER_GATEWAY_QUEUE_DRAIN_PER_TURN", GATEWAY_QUEUE_DRAIN_PER_TURN, min=0)
        except ValueError:
            max_drain = GATEWAY_QUEUE_DRAIN_PER_TURN
        if mode == "followup":
            try:
                from butler.utilities.env_parse import int_env
                from butler.defaults.env_defaults import GATEWAY_QUEUE_DRAIN_FOLLOWUP

                max_drain = max(
                    max_drain,
                    int_env("BUTLER_GATEWAY_QUEUE_DRAIN_FOLLOWUP", GATEWAY_QUEUE_DRAIN_FOLLOWUP, min=0),
                )
            except ValueError:
                pass
        for _ in range(max_drain):
            item = pop_next(session_key)
            if item is None:
                break
            if handler._session_registry.is_session_active(session_key):
                break
            logger.info(
                "Gateway drain queued session=%s priority=%s preview=%r",
                session_key,
                item.priority,
                item.text[:60],
            )
            part = handler.handle_message(
                item.text,
                session_key=session_key,
                platform=item.platform or platform,
                external_id=item.external_id or external_id,
            )
            if part:
                parts.append(part)

    if not parts:
        return ""
    combined = "\n\n---\n\n".join(parts) if len(parts) > 1 else parts[0]
    if handler._queue_push_via_bridge() and primary_reply.strip():
        from butler.gateway.outbound_bridge import get_current_bridge

        br = get_current_bridge()
        if br is not None:
            br.schedule_supplementary_reply(combined, kind="queued")
            return ""
    return combined


__all__ = ["InboundDrainHost", "drain_queued_inbound"]