ForcePilot/backend/package/yuxi/channel/extensions/zoomchat/streaming.py
Kris d5e36d33b7 feat(channel): 添加 Zalo OA、Zoom Chat 和 Zulip 渠道扩展
新增 Zalo OA、Zoom Chat、Zulip 三个渠道扩展。

Zalo OA 渠道扩展主要模块:sidecar_client, config, gateway, outbound, streaming, pairing, security, auth, dedupe, directory, monitor, status, session, reactions, tools

Zoom Chat 渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, crypto, dedupe, actions, media, mentions, monitor, status, session, reactions, threading

Zulip 渠道扩展主要模块:client, config, gateway, outbound, streaming, pairing, security, monitor, status
2026-05-21 12:06:26 +08:00

102 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import asyncio
import logging
logger = logging.getLogger(__name__)
ZOOM_CHUNK_MAX = 3800
ZOOM_CHUNK_MIN = 500
ZOOM_COALESCE_IDLE_MS = 1000
class ZoomStreamSession:
"""
Block Streaming 会话
收集 Agent 流式输出的文本 Token达到 chunk_min 或 idle_ms 时
自动发送当前累积的文本块。
Zoom 的限制:
- 单条消息最大 4096 字符 → chunk_max = 3800 (留余量)
- 不支持消息编辑 (无法实现 Preview Streaming)
- 使用连续发送新消息的方式模拟流式效果
"""
def __init__(self, send_fn, target_id: str, chunk_max: int = ZOOM_CHUNK_MAX):
self._send = send_fn
self._target_id = target_id
self._chunk_max = chunk_max
self._buffer: str = ""
self._sent_count: int = 0
self._finished: bool = False
self._task: asyncio.Task | None = None
async def start(self, initial_text: str = "") -> None:
if initial_text:
self._buffer += initial_text
if len(self._buffer) >= ZOOM_CHUNK_MIN:
await self._flush()
async def feed(self, text: str) -> None:
if self._finished or not text:
return
self._buffer += text
if len(self._buffer) >= ZOOM_CHUNK_MIN:
await self._flush()
async def _flush(self) -> None:
if not self._buffer:
return
chunk = self._buffer[: self._chunk_max]
remainder = self._buffer[self._chunk_max :] if len(self._buffer) > self._chunk_max else ""
if self._sent_count > 0:
prefix = "...\n"
chunk = prefix + chunk[: self._chunk_max - len(prefix)]
try:
await self._send(self._target_id, chunk)
self._sent_count += 1
except Exception:
logger.exception("Zoom streaming send failed")
self._buffer = remainder
async def finish(self) -> None:
while self._buffer:
await self._flush()
self._finished = True
async def cancel(self) -> None:
self._buffer = ""
self._finished = True
@property
def sent_count(self) -> int:
return self._sent_count
@property
def is_finished(self) -> bool:
return self._finished
class ZoomStreaming:
streaming_mode: str = "block"
block_streaming_enabled: bool = True
block_streaming_chunk_min_chars: int = ZOOM_CHUNK_MIN
block_streaming_chunk_max_chars: int = ZOOM_CHUNK_MAX
block_streaming_chunk_break_preference: str = "paragraph"
block_streaming_coalesce_defaults: dict | None = {
"min_chars": ZOOM_CHUNK_MIN,
"max_chars": ZOOM_CHUNK_MAX,
"idle_ms": ZOOM_COALESCE_IDLE_MS,
}
def create_draft_stream_session(self, target_id: str, send_fn=None) -> ZoomStreamSession:
return ZoomStreamSession(send_fn, target_id)
def create_block_chunker(self) -> object:
return {"mode": "length", "min_chars": ZOOM_CHUNK_MIN, "max_chars": ZOOM_CHUNK_MAX}