Kansho/backend/routers/dialogue.py
2026-08-26 10:42:20 +02:00

252 lines
7.4 KiB
Python

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 dialogue_turn import run_turn, visible_for_role
from engine import EngineError
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):
detail = {"code": exc.code, "message": exc.message}
extra = getattr(exc, "diagnostics", None)
if extra:
detail["diagnostics"] = extra
raise HTTPException(status_code=exc.status_code, detail=detail) 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 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)
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.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)