from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from auth import require_auth from context_builder import build_internal_context from continuity import checkpoint_usage_session, close_usage_session from derived_kinds import catalog from detect_learning import pending_for_conversation from dialogue_turn import continue_turn, run_turn, visible_for_role from engine import EngineError from privacy_gateway import public_error_detail from dialogue_store import ( StoreError, append_message, create_conversation, delete_conversation, create_handoff, create_space, create_thread, get_conversation, insert_derived, link_conversation_thread, link_thread_space, list_conversations, list_derived_for_conversation, list_messages, start_usage_session, ) router = APIRouter(prefix="/api/dialogue", tags=["dialogue"]) def _http(exc: StoreError | EngineError): raise HTTPException(status_code=exc.status_code, detail=public_error_detail(exc)) from exc class SessionWrite(BaseModel): intent: str = "" class ConversationWrite(BaseModel): usage_session_id: str | None = None title: str = "" space_id: str | None = None class MessageWrite(BaseModel): body: str = Field(min_length=1) role: str = "user" id: str | None = None class MaskReviewWrite(BaseModel): review_id: str decisions: list[dict] = Field(default_factory=list) class ThreadWrite(BaseModel): title: str = "" status: str = "open" visibility: str = "internal" class SpaceWrite(BaseModel): title: str = "" visibility: str = "internal" class DerivedWrite(BaseModel): kind: str subject_type: str subject_id: str source_message_ids: list[str] body: str = "" visibility: str = "internal" confidence: float | None = None class HandoffWrite(BaseModel): target: str source_conversation_id: str | None = None payload: dict = Field(default_factory=dict) @router.get("/kinds") def list_kinds(session: dict = Depends(require_auth)): return catalog() @router.post("/sessions") def create_session(req: SessionWrite, session: dict = Depends(require_auth)): return start_usage_session(session["profile_id"], req.intent) @router.post("/sessions/{usage_session_id}/end") def end_session(usage_session_id: str, session: dict = Depends(require_auth)): try: return close_usage_session(session["profile_id"], usage_session_id) except StoreError as exc: _http(exc) @router.post("/sessions/{usage_session_id}/checkpoint") def checkpoint_session(usage_session_id: str, session: dict = Depends(require_auth)): try: return checkpoint_usage_session(session["profile_id"], usage_session_id) except StoreError as exc: _http(exc) @router.post("/conversations") def post_conversation(req: ConversationWrite, session: dict = Depends(require_auth)): try: return create_conversation( session["profile_id"], req.usage_session_id, req.title, space_id=req.space_id, ) except StoreError as exc: _http(exc) @router.get("/conversations") def get_conversations(session: dict = Depends(require_auth)): return list_conversations(session["profile_id"]) @router.delete("/conversations/{conversation_id}") def remove_conversation(conversation_id: str, session: dict = Depends(require_auth)): try: from writing_profile_store import remember_dialogue_style result = delete_conversation(session["profile_id"], conversation_id) remember_dialogue_style(session["profile_id"]) return result except StoreError as exc: _http(exc) @router.get("/conversations/{conversation_id}") def get_one_conversation(conversation_id: str, session: dict = Depends(require_auth)): try: conv = get_conversation(session["profile_id"], conversation_id) conv["messages"] = list_messages(session["profile_id"], conversation_id) conv["derived"] = list_derived_for_conversation(session["profile_id"], conversation_id) pending = pending_for_conversation(session["profile_id"], conversation_id) if pending: conv["pending_mask_review"] = pending return conv except StoreError as exc: _http(exc) @router.get("/conversations/{conversation_id}/context") def get_internal_context(conversation_id: str, session: dict = Depends(require_auth)): try: return build_internal_context(session["profile_id"], conversation_id) except StoreError as exc: _http(exc) @router.post("/conversations/{conversation_id}/messages") def post_message(conversation_id: str, req: MessageWrite, session: dict = Depends(require_auth)): try: return append_message(session["profile_id"], conversation_id, req.body, req.role, req.id) except StoreError as exc: _http(exc) @router.post("/conversations/{conversation_id}/turn") def conversation_turn(conversation_id: str, req: MessageWrite, session: dict = Depends(require_auth)): try: return visible_for_role( run_turn(session["profile_id"], conversation_id, req.body, message_id=req.id), session.get("role"), ) except (StoreError, EngineError) as exc: _http(exc) @router.post("/conversations/{conversation_id}/turn/review") def conversation_turn_review(conversation_id: str, req: MaskReviewWrite, session: dict = Depends(require_auth)): try: return visible_for_role( continue_turn(session["profile_id"], conversation_id, req.review_id, req.decisions), session.get("role"), ) except (StoreError, EngineError) as exc: _http(exc) @router.get("/conversations/{conversation_id}/messages") def get_messages(conversation_id: str, session: dict = Depends(require_auth)): try: return list_messages(session["profile_id"], conversation_id) except StoreError as exc: _http(exc) @router.post("/threads") def post_thread(req: ThreadWrite, session: dict = Depends(require_auth)): try: return create_thread(session["profile_id"], req.title, req.status, req.visibility) except StoreError as exc: _http(exc) @router.post("/conversations/{conversation_id}/threads/{thread_id}") def post_conversation_thread(conversation_id: str, thread_id: str, session: dict = Depends(require_auth)): try: return link_conversation_thread(session["profile_id"], conversation_id, thread_id) except StoreError as exc: _http(exc) @router.post("/spaces") def post_space(req: SpaceWrite, session: dict = Depends(require_auth)): try: return create_space(session["profile_id"], req.title, req.visibility) except StoreError as exc: _http(exc) @router.post("/threads/{thread_id}/spaces/{space_id}") def post_thread_space(thread_id: str, space_id: str, confidence: float | None = None, session: dict = Depends(require_auth)): try: return link_thread_space(session["profile_id"], thread_id, space_id, confidence) except StoreError as exc: _http(exc) @router.post("/derived") def post_derived(req: DerivedWrite, session: dict = Depends(require_auth)): try: return insert_derived( session["profile_id"], req.kind, req.subject_type, req.subject_id, req.source_message_ids, req.body, req.visibility, req.confidence, ) except StoreError as exc: _http(exc) @router.get("/derived/latest") def get_latest(subject_type: str, subject_id: str, kind: str, session: dict = Depends(require_auth)): from data_layer import read return read( "latest_derived", profile_id=session["profile_id"], context={"subject_type": subject_type, "subject_id": subject_id, "kind": kind}, ) @router.post("/handoffs") def post_handoff(req: HandoffWrite, session: dict = Depends(require_auth)): try: return create_handoff(session["profile_id"], req.target, req.source_conversation_id, req.payload) except StoreError as exc: _http(exc)