Newer
Older
navi-1 / navi / api / routes / sessions.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy on 8 Apr 3 KB Pin sessions + larger code font
"""Session management endpoints."""

from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel

from navi.api.deps import get_profile_registry, get_session_store
from navi.core import ProfileRegistry, SessionStore
from navi.exceptions import ProfileNotFound

router = APIRouter(prefix="/sessions", tags=["sessions"])


class CreateSessionRequest(BaseModel):
    profile_id: str


class PinSessionRequest(BaseModel):
    pinned: bool


@router.post("", status_code=201)
async def create_session(
    body: CreateSessionRequest,
    store: Annotated[SessionStore, Depends(get_session_store)],
    profiles: Annotated[ProfileRegistry, Depends(get_profile_registry)],
) -> dict:
    try:
        profiles.get(body.profile_id)
    except ProfileNotFound:
        raise HTTPException(status_code=404, detail=f"Profile '{body.profile_id}' not found")

    session = await store.create(body.profile_id)
    return {
        "session_id": session.id,
        "profile_id": session.profile_id,
        "created_at": session.created_at.isoformat(),
    }


@router.get("")
async def list_sessions(
    store: Annotated[SessionStore, Depends(get_session_store)],
) -> list[dict]:
    sessions = await store.list_all()
    return [
        {
            "session_id": s.id,
            "profile_id": s.profile_id,
            "message_count": len(s.messages),
            "preview": _preview(s),
            "pinned": s.pinned,
            "created_at": s.created_at.isoformat(),
            "last_active": s.last_active.isoformat(),
        }
        for s in sessions
    ]


def _preview(session) -> str:
    """Return a short snippet from the last user or assistant message."""
    for msg in reversed(session.messages):
        if msg.role in ("user", "assistant") and msg.content:
            return msg.content[:60]
    return ""


@router.get("/{session_id}")
async def get_session(
    session_id: str,
    store: Annotated[SessionStore, Depends(get_session_store)],
) -> dict:
    session = await store.get(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    return {
        "session_id": session.id,
        "profile_id": session.profile_id,
        "messages": [m.model_dump(exclude_none=True) for m in session.messages],
        "created_at": session.created_at.isoformat(),
        "last_active": session.last_active.isoformat(),
    }


@router.patch("/{session_id}/pin")
async def pin_session(
    session_id: str,
    body: PinSessionRequest,
    store: Annotated[SessionStore, Depends(get_session_store)],
) -> dict:
    ok = await store.set_pinned(session_id, body.pinned)
    if not ok:
        raise HTTPException(status_code=404, detail="Session not found")
    return {"session_id": session_id, "pinned": body.pinned}


@router.delete("/{session_id}", status_code=204)
async def delete_session(
    session_id: str,
    store: Annotated[SessionStore, Depends(get_session_store)],
) -> None:
    deleted = await store.delete(session_id)
    if not deleted:
        raise HTTPException(status_code=404, detail="Session not found")