新增 Jira 渠道扩展,支持在 Yuxi 平台中集成 Jira 项目管理渠道。 包含以下功能模块: - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - outbound: 外发消息与工单管理 - streaming: 流式消息处理 - parse: Jira 内容解析 - format: 消息格式转换 - agent_tools: Agent 工具集成 - security: 安全校验 - dedupe: 消息去重 - loopbreaker: 循环响应防护 - monitor: 渠道状态监控 - status: 会话与工单状态管理 - types: 类型定义
124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
|
|
from yuxi.channel.protocols import StreamingProtocol
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class JiraStreaming(StreamingProtocol):
|
|
streaming_mode = "block"
|
|
preview_stream_throttle_ms = 160
|
|
preview_min_initial_chars = 18
|
|
block_streaming_enabled = True
|
|
block_streaming_break = "paragraph"
|
|
block_streaming_chunk_min_chars = 400
|
|
block_streaming_chunk_max_chars = 4000
|
|
block_streaming_chunk_break_preference = "paragraph"
|
|
block_streaming_coalesce_defaults = {"min_chars": 200, "max_chars": 2000}
|
|
block_streaming_update_interval_sec = 3.0
|
|
|
|
def __init__(self, outbound=None):
|
|
self._outbound = outbound
|
|
|
|
def create_draft_stream_session(self, target_id: str) -> dict:
|
|
return {
|
|
"target_id": target_id,
|
|
"mode": "block",
|
|
"chunks_sent": 0,
|
|
"placeholder_comment_id": None,
|
|
}
|
|
|
|
def create_block_chunker(self) -> object:
|
|
return JiraBlockChunkerConfig(
|
|
min_chars=self.block_streaming_chunk_min_chars,
|
|
max_chars=self.block_streaming_chunk_max_chars,
|
|
mode="interval",
|
|
interval_sec=self.block_streaming_update_interval_sec,
|
|
)
|
|
|
|
def create_block_chunker_instance(
|
|
self,
|
|
issue_key: str,
|
|
account: dict,
|
|
outbound=None,
|
|
) -> JiraBlockChunker:
|
|
ob = outbound or self._outbound
|
|
return JiraBlockChunker(
|
|
outbound=ob,
|
|
issue_key=issue_key,
|
|
account=account,
|
|
update_interval_sec=self.block_streaming_update_interval_sec,
|
|
)
|
|
|
|
|
|
class JiraBlockChunkerConfig:
|
|
def __init__(self, min_chars: int, max_chars: int, mode: str = "interval", interval_sec: float = 3.0):
|
|
self.min_chars = min_chars
|
|
self.max_chars = max_chars
|
|
self.mode = mode
|
|
self.interval_sec = interval_sec
|
|
|
|
|
|
class JiraBlockChunker:
|
|
def __init__(
|
|
self,
|
|
outbound,
|
|
issue_key: str,
|
|
account: dict,
|
|
update_interval_sec: float = 3.0,
|
|
max_session_sec: float = 600.0,
|
|
):
|
|
self._outbound = outbound
|
|
self._issue_key = issue_key
|
|
self._account = account
|
|
self._update_interval_sec = update_interval_sec
|
|
self._max_session_sec = max_session_sec
|
|
self._buffer = ""
|
|
self._message_id: str | None = None
|
|
self._last_update = 0.0
|
|
self._start_time = 0.0
|
|
self._finished = False
|
|
|
|
async def start(self):
|
|
result = await self._outbound.send_text(
|
|
self._issue_key,
|
|
"🤖 AI 正在分析中...",
|
|
account_id=self._account.get("account_id"),
|
|
)
|
|
if result and result.get("success"):
|
|
self._message_id = result["msg_id"]
|
|
self._start_time = time.monotonic()
|
|
self._last_update = self._start_time
|
|
|
|
async def append(self, chunk: str) -> None:
|
|
self._buffer += chunk
|
|
now = time.monotonic()
|
|
if now - self._last_update < self._update_interval_sec:
|
|
return
|
|
if now - self._start_time > self._max_session_sec:
|
|
await self.finalize()
|
|
return
|
|
if self._message_id:
|
|
await self._outbound.edit_message(
|
|
self._issue_key,
|
|
self._message_id,
|
|
self._buffer + "\n\n⏳ *分析进行中...*",
|
|
account_id=self._account.get("account_id"),
|
|
)
|
|
self._last_update = now
|
|
|
|
async def finalize(self) -> None:
|
|
if self._finished:
|
|
return
|
|
self._finished = True
|
|
if self._message_id and self._buffer:
|
|
await self._outbound.edit_message(
|
|
self._issue_key,
|
|
self._message_id,
|
|
self._buffer,
|
|
account_id=self._account.get("account_id"),
|
|
)
|