183 lines
5.4 KiB
Python
183 lines
5.4 KiB
Python
"""
|
|
Sessions Controller for Mid Platform.
|
|
[AC-IDMP-09] Session mode switch endpoint: POST /mid/sessions/{sessionId}/mode
|
|
"""
|
|
|
|
import logging
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, Path
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_session
|
|
from app.core.tenant import get_tenant_id
|
|
from app.models.mid.schemas import (
|
|
SessionMode,
|
|
SwitchModeRequest,
|
|
SwitchModeResponse,
|
|
)
|
|
from app.services.memory import MemoryService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/mid", tags=["Mid Platform Sessions"])
|
|
|
|
_session_modes: dict[str, SessionMode] = {}
|
|
|
|
|
|
class CancelFlowResponse(BaseModel):
|
|
"""Response for cancel flow operation."""
|
|
|
|
success: bool
|
|
message: str
|
|
session_id: str
|
|
|
|
|
|
@router.post(
|
|
"/sessions/{sessionId}/cancel-flow",
|
|
operation_id="cancelActiveFlow",
|
|
summary="Cancel active flow",
|
|
description="""
|
|
Cancel the active flow for a session.
|
|
|
|
Use this when you encounter "Session already has an active flow" error.
|
|
""",
|
|
responses={
|
|
200: {"description": "Flow cancelled successfully", "model": CancelFlowResponse},
|
|
404: {"description": "No active flow found"},
|
|
},
|
|
)
|
|
async def cancel_active_flow(
|
|
sessionId: Annotated[str, Path(description="Session ID")],
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
) -> CancelFlowResponse:
|
|
"""
|
|
Cancel the active flow for a session.
|
|
|
|
This endpoint allows you to cancel any active flow instance
|
|
so that a new flow can be started.
|
|
"""
|
|
tenant_id = get_tenant_id()
|
|
|
|
if not tenant_id:
|
|
from app.core.exceptions import MissingTenantIdException
|
|
raise MissingTenantIdException()
|
|
|
|
logger.info(
|
|
f"[Cancel Flow] Cancelling active flow: tenant={tenant_id}, session={sessionId}"
|
|
)
|
|
|
|
try:
|
|
from app.services.flow.engine import FlowEngine
|
|
|
|
flow_engine = FlowEngine(session)
|
|
cancelled = await flow_engine.cancel_flow(
|
|
tenant_id=tenant_id,
|
|
session_id=sessionId,
|
|
reason="User requested cancellation via API",
|
|
)
|
|
|
|
if cancelled:
|
|
logger.info(f"[Cancel Flow] Flow cancelled: session={sessionId}")
|
|
return CancelFlowResponse(
|
|
success=True,
|
|
message="Active flow cancelled successfully",
|
|
session_id=sessionId,
|
|
)
|
|
else:
|
|
logger.info(f"[Cancel Flow] No active flow found: session={sessionId}")
|
|
return CancelFlowResponse(
|
|
success=True,
|
|
message="No active flow found for this session",
|
|
session_id=sessionId,
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Cancel Flow] Failed to cancel flow: {e}")
|
|
return CancelFlowResponse(
|
|
success=False,
|
|
message=f"Failed to cancel flow: {str(e)}",
|
|
session_id=sessionId,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/sessions/{sessionId}/mode",
|
|
operation_id="switchSessionMode",
|
|
summary="Switch session mode",
|
|
description="""
|
|
[AC-IDMP-09] Switch session mode between BOT_ACTIVE and HUMAN_ACTIVE.
|
|
|
|
When mode is HUMAN_ACTIVE, dialogue responses will route to transfer mode.
|
|
""",
|
|
responses={
|
|
200: {"description": "Mode switched successfully", "model": SwitchModeResponse},
|
|
400: {"description": "Invalid request"},
|
|
},
|
|
)
|
|
async def switch_session_mode(
|
|
sessionId: Annotated[str, Path(description="Session ID")],
|
|
switch_request: SwitchModeRequest,
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
) -> SwitchModeResponse:
|
|
"""
|
|
[AC-IDMP-09] Switch session mode.
|
|
|
|
Modes:
|
|
- BOT_ACTIVE: Bot handles responses
|
|
- HUMAN_ACTIVE: Transfer to human agent
|
|
"""
|
|
tenant_id = get_tenant_id()
|
|
|
|
if not tenant_id:
|
|
from app.core.exceptions import MissingTenantIdException
|
|
raise MissingTenantIdException()
|
|
|
|
logger.info(
|
|
f"[AC-IDMP-09] Mode switch: tenant={tenant_id}, "
|
|
f"session={sessionId}, mode={switch_request.mode.value}"
|
|
)
|
|
|
|
try:
|
|
memory_service = MemoryService(session)
|
|
|
|
await memory_service.get_or_create_session(
|
|
tenant_id=tenant_id,
|
|
session_id=sessionId,
|
|
)
|
|
|
|
session_key = f"{tenant_id}:{sessionId}"
|
|
_session_modes[session_key] = switch_request.mode
|
|
|
|
logger.info(
|
|
f"[AC-IDMP-09] Mode switched: session={sessionId}, "
|
|
f"mode={switch_request.mode.value}, reason={switch_request.reason}"
|
|
)
|
|
|
|
return SwitchModeResponse(
|
|
session_id=sessionId,
|
|
mode=switch_request.mode,
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[AC-IDMP-09] Mode switch failed: {e}")
|
|
return SwitchModeResponse(
|
|
session_id=sessionId,
|
|
mode=switch_request.mode,
|
|
)
|
|
|
|
|
|
def get_session_mode(tenant_id: str, session_id: str) -> SessionMode:
|
|
"""Get current session mode."""
|
|
session_key = f"{tenant_id}:{session_id}"
|
|
return _session_modes.get(session_key, SessionMode.BOT_ACTIVE)
|
|
|
|
|
|
def clear_session_mode(tenant_id: str, session_id: str) -> None:
|
|
"""Clear session mode (reset to BOT_ACTIVE)."""
|
|
session_key = f"{tenant_id}:{session_id}"
|
|
if session_key in _session_modes:
|
|
del _session_modes[session_key]
|