from __future__ import annotations from dataclasses import dataclass from yuxi.channel.capabilities import ChannelCapabilities STREAMING_MODE_CARD_KIT = "card_kit" STREAMING_MODE_BLOCK = "block" STREAMING_MODE_C2C_STREAM_API = "c2c_stream_api" STREAMING_MODE_RAW = "raw" STREAMING_MODE_PREVIEW = "preview" _MODE_TO_STRATEGY: dict[str, str] = { "card_kit": STREAMING_MODE_CARD_KIT, "block": STREAMING_MODE_BLOCK, "c2c_stream_api": STREAMING_MODE_C2C_STREAM_API, "raw": STREAMING_MODE_RAW, } @dataclass class StreamingStrategy: mode: str preview_throttle_ms: int = 160 preview_min_initial_chars: int = 18 block_chunk_min_chars: int = 800 block_chunk_max_chars: int = 1200 block_chunk_break_preference: str = "paragraph" block_coalesce_min_chars: int | None = None block_coalesce_max_chars: int | None = None block_coalesce_idle_ms: int = 1000 @classmethod def from_capabilities( cls, caps: ChannelCapabilities, overrides: dict | None = None, ) -> StreamingStrategy: mode = _MODE_TO_STRATEGY.get(caps.streaming_mode or "", STREAMING_MODE_PREVIEW) strategy = cls( mode=mode, preview_throttle_ms=caps.preview_stream_throttle_ms, preview_min_initial_chars=caps.preview_min_initial_chars, block_chunk_min_chars=caps.block_streaming_chunk_min_chars, block_chunk_max_chars=caps.block_streaming_chunk_max_chars, block_chunk_break_preference=caps.block_streaming_chunk_break_preference, block_coalesce_min_chars=caps.block_streaming_coalesce_min_chars, block_coalesce_max_chars=caps.block_streaming_coalesce_max_chars, block_coalesce_idle_ms=caps.block_streaming_coalesce_idle_ms, ) if overrides: for k, v in overrides.items(): if hasattr(strategy, k) and v is not None: setattr(strategy, k, v) return strategy @property def is_card_kit(self) -> bool: return self.mode == STREAMING_MODE_CARD_KIT @property def is_block(self) -> bool: return self.mode == STREAMING_MODE_BLOCK @property def is_raw(self) -> bool: return self.mode == STREAMING_MODE_RAW @property def is_c2c_stream_api(self) -> bool: return self.mode == STREAMING_MODE_C2C_STREAM_API @property def is_preview(self) -> bool: return self.mode == STREAMING_MODE_PREVIEW def to_dict(self) -> dict: result = {"mode": self.mode, "preview_throttle_ms": self.preview_throttle_ms} if self.preview_min_initial_chars != 18: result["preview_min_initial_chars"] = self.preview_min_initial_chars if self.mode in (STREAMING_MODE_BLOCK, STREAMING_MODE_CARD_KIT): result.update({ "block_chunk_min_chars": self.block_chunk_min_chars, "block_chunk_max_chars": self.block_chunk_max_chars, "block_chunk_break_preference": self.block_chunk_break_preference, "block_coalesce_idle_ms": self.block_coalesce_idle_ms, }) if self.block_coalesce_min_chars is not None: result["block_coalesce_min_chars"] = self.block_coalesce_min_chars if self.block_coalesce_max_chars is not None: result["block_coalesce_max_chars"] = self.block_coalesce_max_chars return result class StreamingStrategySelector: @staticmethod def select(capabilities: ChannelCapabilities) -> StreamingStrategy: return StreamingStrategy.from_capabilities(capabilities) @staticmethod def supports_preview(capabilities: ChannelCapabilities) -> bool: return capabilities.streaming and ( capabilities.streaming_mode is None or capabilities.streaming_mode == STREAMING_MODE_RAW ) @staticmethod def supports_block(capabilities: ChannelCapabilities) -> bool: return bool(capabilities.block_streaming) and ( capabilities.streaming_mode == STREAMING_MODE_BLOCK or (capabilities.streaming_mode is None and capabilities.block_streaming) ) @staticmethod def supports_card_kit(capabilities: ChannelCapabilities) -> bool: return capabilities.streaming_mode == STREAMING_MODE_CARD_KIT @staticmethod def supports_c2c_stream(capabilities: ChannelCapabilities) -> bool: return capabilities.streaming_mode == STREAMING_MODE_C2C_STREAM_API @staticmethod def get_config(capabilities: ChannelCapabilities) -> dict: return StreamingStrategy.from_capabilities(capabilities).to_dict()