ForcePilot/backend/package/yuxi/channel/extensions/tlon/history.py
Kris 0babb1ca8e feat(channel): 添加 Tlon 渠道扩展
新增 Tlon 渠道扩展,支持在 Yuxi 平台中集成 Tlon/Urbit 去中心化通讯平台。

包含以下功能模块:
- tlon_api: Tlon API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- sse_client: SSE 客户端
- outbound: 外发消息管理
- send: 消息发送
- security: 安全校验
- auth: 认证管理
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- approval: 审批流程
- channel_mgmt: 频道管理
- channel_ops: 频道操作
- contacts: 联系人管理
- discovery: 服务发现
- doctor: 健康诊断
- expose: 服务暴露
- gallery: 图库管理
- history: 历史记录
- hooks: 钩子管理
- media: 媒体资源处理
- notebook: 笔记本功能
- settings_store: 设置存储
- setup: 初始化设置
- story: 故事功能
- targets: 目标管理
- cite_parser: 引用解析
- utils: 工具函数
- types: 类型定义
2026-05-21 11:55:25 +08:00

114 lines
3.8 KiB
Python

import urllib.parse
import re
import logging
from yuxi.channel.extensions.tlon.story import story_to_text
logger = logging.getLogger(__name__)
MAX_THREAD_HISTORY = 20
THREAD_CONTEXT_LIMIT = 10
async def fetch_changes(client, nest: str, since: int = 0) -> list[dict]:
try:
result = await client.scry(f"/{nest}/changes/{since}")
return result.get("changes", [])
except Exception as e:
logger.warning("[tlon] Failed to scry changes for %s: %s", nest, e)
return []
async def fetch_range(client, nest: str, start: int, end: int) -> list[dict]:
try:
result = await client.scry(f"/{nest}/range/{start}/{end}")
return result.get("posts", result.get("messages", []))
except Exception as e:
logger.warning("[tlon] Failed to scry range for %s: %s", nest, e)
return []
async def fetch_message_context(client, nest: str, msg_id: str,
before: int = 5, after: int = 3) -> list[dict]:
try:
msg_data = await client.scry(f"/{nest}/post/{msg_id}")
idx = msg_data.get("index", 0)
start = max(0, idx - before)
end = idx + after + 1
return await fetch_range(client, nest, start, end)
except Exception:
return []
async def search_messages(client, query: str, nest: str = "",
limit: int = 20) -> list[dict]:
encoded = urllib.parse.quote(query)
path = f"/chat/search/{encoded}"
if nest:
path = f"/{nest}/search/{encoded}"
try:
data = await client.scry(path)
results = data.get("results", data.get("posts", []))
return results[:limit]
except Exception:
logger.warning("[tlon] Search not available (may require Tlon v10.2+)")
return []
async def fetch_thread_history(messages: list[dict],
parent_id: str) -> list[dict]:
thread_messages = []
for msg in messages:
if msg.get("parent_id") == parent_id or msg.get("message_id") == parent_id:
thread_messages.append(msg)
for reply in msg.get("replies", []):
if reply.get("parent_id") == parent_id:
thread_messages.append(reply)
thread_messages.sort(key=lambda m: m.get("timestamp", 0))
return thread_messages[-MAX_THREAD_HISTORY:]
def format_thread_context(messages: list[dict]) -> str:
recent = messages[-THREAD_CONTEXT_LIMIT:]
lines = []
for msg in recent:
author = msg.get("author", "unknown")
content = msg.get("content", "")
lines.append(f"{author}: {content}")
header = (
f"[Thread conversation - {len(messages)} previous replies. "
"You are participating in this thread. "
"Only respond if relevant or helpful - you don't need to reply to every message.]\n\n"
)
return header + "[Previous messages]\n" + "\n".join(lines)
async def fetch_channel_history(channel_messages: list[dict],
limit: int = 50) -> list[dict]:
sorted_msgs = sorted(channel_messages, key=lambda m: m.get("timestamp", 0))
return sorted_msgs[-limit:]
def is_summarization_request(text: str) -> bool:
patterns = [
r"summarize\s+(this\s+)?(channel|chat|conversation)",
r"what\s+did\s+i\s+miss",
r"catch\s+me\s+up",
r"channel\s+summary",
r"tldr",
]
return any(re.search(p, text, re.IGNORECASE) for p in patterns)
def format_channel_history_for_summary(messages: list[dict]) -> str:
lines = []
for msg in messages:
ts = msg.get("timestamp", 0)
author = msg.get("author", "unknown")
content = msg.get("content", "")
import datetime
dt = datetime.datetime.fromtimestamp(ts / 1000)
date_str = dt.strftime("%Y-%m-%d %H:%M")
lines.append(f"[{date_str}] {author}: {content}")
return "\n".join(lines)