feat(nostr adapter): 新增Nostr会话处理和线程模拟工具

新增两个文件实现Nostr适配器的基础工具能力:
1. session.py:实现聊天ID、线程键、代理路由的解析与目标标准化
2. thread_simulator.py:实现Nostr事件线程链构建、标签解析和事件缓存获取功能
This commit is contained in:
Kris 2026-05-13 16:13:17 +08:00
parent 7002557cf4
commit b44aa607e0
2 changed files with 100 additions and 0 deletions

View File

@ -0,0 +1,31 @@
from __future__ import annotations
from yuxi.channels.models import ChannelMessage
def resolve_thread_key(message: ChannelMessage) -> str:
identity = message.identity
chat_id = identity.channel_chat_id
if chat_id.startswith("dm:"):
pubkey = chat_id.removeprefix("dm:")
return f"nostr:dm:{pubkey}"
if chat_id.startswith("channel:"):
channel = chat_id.removeprefix("channel:")
return f"nostr:channel:{channel}"
return f"nostr:unknown:{chat_id}"
def resolve_chat_id(message: ChannelMessage) -> str:
return message.identity.channel_chat_id
def resolve_agent_route(message: ChannelMessage, default_agent_id: str = "default") -> str:
thread_key = resolve_thread_key(message)
return f"agent:{default_agent_id}:{thread_key}"
def normalize_target(target: str) -> str:
if target.startswith("nostr:"):
return target
return f"nostr:{target}"

View File

@ -0,0 +1,69 @@
from __future__ import annotations
from typing import Any
class NostrThreadSimulator:
def __init__(self, relay_pool: Any = None, max_depth: int = 10):
self._relay_pool = relay_pool
self._event_cache: dict[str, dict] = {}
self._reply_chains: dict[str, list[str]] = {}
self._max_depth = max_depth
async def build_thread_from_event(self, event: dict) -> list[dict]:
chain: list[dict] = []
current = event
for _ in range(self._max_depth):
chain.append(current)
parent_id = self._get_parent_event_id(current)
if not parent_id:
break
parent = await self._fetch_event(parent_id)
if not parent:
break
current = parent
return list(reversed(chain))
@staticmethod
def _get_parent_event_id(event: dict) -> str | None:
tags: list[list[str]] = event.get("tags", [])
for tag in tags:
if tag[0] == "e":
if len(tag) >= 4 and tag[3] == "reply":
return tag[1]
if len(tag) >= 2:
return tag[1]
return None
@staticmethod
def resolve_e_tags(event: dict) -> list[str]:
tags: list[list[str]] = event.get("tags", [])
return [tag[1] for tag in tags if tag[0] == "e" and len(tag) > 1]
@staticmethod
def resolve_p_tags(event: dict) -> list[str]:
tags: list[list[str]] = event.get("tags", [])
return [tag[1] for tag in tags if tag[0] == "p" and len(tag) > 1]
def _is_root_event(self, event: dict) -> bool:
tags: list[list[str]] = event.get("tags", [])
for tag in tags:
if tag[0] == "e" and len(tag) >= 4 and tag[3] == "root":
return False
return True
async def _fetch_event(self, event_id: str) -> dict | None:
if event_id in self._event_cache:
return self._event_cache[event_id]
if self._relay_pool:
try:
event = await self._relay_pool.query_event(event_id)
if event:
self._event_cache[event_id] = event
return event
except Exception:
pass
return None