新增 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: 类型定义
114 lines
3.8 KiB
Python
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) |