Spaces:
Sleeping
Sleeping
| """ | |
| Nancy HF Space β Session REST API Router. | |
| Provides session management endpoints so agents can: | |
| - Create new tracked conversation sessions | |
| - List all sessions (optionally filtered by provider) | |
| - Get a specific session's state | |
| - Delete / archive sessions | |
| - Update a session's conversation URL (called internally after task completion) | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from typing import Any | |
| from fastapi import APIRouter, Depends, HTTPException, Query, status | |
| from pydantic import BaseModel, Field | |
| from core.auth import require_api_key | |
| from core.sessions import session_store | |
| logger = logging.getLogger("nancy.sessions_router") | |
| router = APIRouter(prefix="/v1/sessions", tags=["Session Management"]) | |
| # ββ Request / Response Schemas ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class CreateSessionRequest(BaseModel): | |
| """Request body for creating a new conversation session.""" | |
| provider: str = Field( | |
| ..., | |
| description="Target provider key (e.g. 'chatgpt', 'gemini', 'nim'). " | |
| "Determines which browser tab / official API to use." | |
| ) | |
| title: str | None = Field( | |
| default=None, | |
| description="Optional human-readable title for this session." | |
| ) | |
| system_prompt: str | None = Field( | |
| default=None, | |
| description="Optional system prompt to prepend when starting a new chat." | |
| ) | |
| class UpdateSessionURLRequest(BaseModel): | |
| """Internal: update a session's conversation URL after a chat.""" | |
| conversation_url: str = Field( | |
| ..., | |
| description="The browser tab URL for the active conversation (e.g. chatgpt.com/c/abc123)." | |
| ) | |
| message_count_delta: int = Field( | |
| default=1, | |
| description="How many messages to add to the running total." | |
| ) | |
| # ββ Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def create_session( | |
| body: CreateSessionRequest, | |
| api_key: str = Depends(require_api_key), | |
| ) -> dict[str, Any]: | |
| """ | |
| Create a new conversation session. | |
| Returns the session_id and metadata. Use this session_id in subsequent | |
| chat completion requests (as the `user` field or via custom header) to | |
| automatically resume the same conversation tab. | |
| """ | |
| session = await session_store.create_session( | |
| provider=body.provider, | |
| title=body.title, | |
| system_prompt=body.system_prompt, | |
| ) | |
| logger.info("Created session '%s' for provider '%s'", session.session_id[:8], body.provider) | |
| return { | |
| "session_id": session.session_id, | |
| "message": f"Session created for provider '{body.provider}'.", | |
| "session": session.to_dict(), | |
| } | |
| async def list_sessions( | |
| provider: str | None = Query(default=None, description="Filter by provider key."), | |
| api_key: str = Depends(require_api_key), | |
| ) -> dict[str, Any]: | |
| """ | |
| List all tracked sessions, optionally filtered by provider. | |
| Sessions are sorted by last_used_at (most recent first). | |
| """ | |
| sessions = await session_store.list_sessions(provider=provider) | |
| return { | |
| "total": len(sessions), | |
| "sessions": [s.to_dict() for s in sessions], | |
| } | |
| async def get_session( | |
| session_id: str, | |
| api_key: str = Depends(require_api_key), | |
| ) -> dict[str, Any]: | |
| """ | |
| Retrieve a specific session by its ID. | |
| """ | |
| session = await session_store.get_session(session_id) | |
| if not session: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail=f"Session '{session_id}' not found.", | |
| ) | |
| return session.to_dict() | |
| async def update_session_url( | |
| session_id: str, | |
| body: UpdateSessionURLRequest, | |
| api_key: str = Depends(require_api_key), | |
| ) -> dict[str, Any]: | |
| """ | |
| Update a session's conversation URL. | |
| Called after a task completes to record the browser URL for the conversation, | |
| enabling future resume operations. | |
| """ | |
| session = await session_store.get_session(session_id) | |
| if not session: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail=f"Session '{session_id}' not found.", | |
| ) | |
| await session_store.update_session_url( | |
| session_id=session_id, | |
| conversation_url=body.conversation_url, | |
| message_count_delta=body.message_count_delta, | |
| ) | |
| updated = await session_store.get_session(session_id) | |
| return { | |
| "message": "Session URL updated.", | |
| "session": updated.to_dict() if updated else {}, | |
| } | |
| async def delete_session( | |
| session_id: str, | |
| api_key: str = Depends(require_api_key), | |
| ) -> dict[str, Any]: | |
| """ | |
| Archive / soft-delete a session. | |
| The session record is kept but marked as archived and excluded from list results. | |
| """ | |
| deleted = await session_store.delete_session(session_id) | |
| if not deleted: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail=f"Session '{session_id}' not found.", | |
| ) | |
| return {"message": f"Session '{session_id[:8]}...' archived successfully."} | |