"""叩心枢会话/消息创建与一炁事件推送。 设计 docs/domain/core-domains.md§7。 主动会话优先复用已有活跃会话,避免每次触发都新建会话。 只有在角色无活跃会话时才会创建新会话。 """ from __future__ import annotations from datetime import UTC, datetime from typing import Any from sqlalchemy.ext.asyncio import AsyncSession from app.core.logging import logger from app.models.chat import ChatMessage, ChatSession from app.modules.runtime.events import Event from app.services.event_store import DbEventStore async def create_proactive_message( session: AsyncSession, event_store: DbEventStore, character_id: str, opener_text: str, condition_name: str, *, session_id: str | None = None, now: datetime | None = None, ) -> dict[str, Any]: """创建叩心枢主动发起的消息,可追加到现有会话或新建会话。 策略: - 若 ``session_id`` 不为空,追加消息到该会话(不创建新会话) - 若 ``session_id`` 为空,新建会话 + 消息 Returns: {"session_id": str, "message_id": str}: 实际写入的会话 ID 和消息 ID。 """ now = now or datetime.now(UTC) is_new = session_id is None if is_new: # 创建新会话(status 默认 'active') chat_session = ChatSession( character_id=character_id, title=opener_text[:60], ) session.add(chat_session) await session.flush() session_id = chat_session.id event_type = "proactive.session" else: event_type = "proactive.message" # 创建开场消息(无 run_id——非流式、无推理过程) message = ChatMessage( character_id=character_id, session_id=session_id, role="assistant", modality="text", content=opener_text, ) session.add(message) await session.flush() # 推一炁事件 payload: dict[str, Any] = { "session_id": session_id, "message_id": message.id, "character_id": character_id, "preview": opener_text[:100], "trigger": condition_name, "is_new": is_new, } if is_new: payload["title_preview"] = opener_text[:60] event = Event( type=event_type, source="knocking_pivot", character_id=character_id, payload=payload, visibility="public", created_at=now.isoformat(), ) # 复用调用方的 session——同一事务写 events 表,避免 session A 写 chat 表、 # session B 写 events 表时两条连接争写导致锁冲突 await event_store.append(event, session=session) logger.info( f"knocking_pivot_{'session_created' if is_new else 'message_appended'}", character_id=character_id, session_id=session_id, message_id=message.id, trigger=condition_name, ) return {"session_id": session_id, "message_id": message.id}