""" Call Management API — Place calls, check status, transfer, hold-slay. """ from fastapi import APIRouter, Depends, HTTPException from api.deps import get_gateway from core.gateway import AIPSTNGateway from models.call import ( CallMode, CallRequest, CallResponse, CallStatusResponse, HoldSlayerRequest, TransferRequest, ) router = APIRouter() @router.post("/outbound", response_model=CallResponse) async def make_call( request: CallRequest, gateway: AIPSTNGateway = Depends(get_gateway), ): """ Place an outbound call. Modes: - **direct**: Call and connect to your device immediately - **hold_slayer**: Navigate IVR, wait on hold, transfer when human detected - **ai_assisted**: Connect with noise cancel, transcription, recording """ try: call = await gateway.make_call( number=request.number, mode=request.mode, intent=request.intent, device=request.device, call_flow_id=request.call_flow_id, services=request.services, ) return CallResponse( call_id=call.id, status=call.status.value, number=request.number, mode=request.mode.value, ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/hold-slayer", response_model=CallResponse) async def hold_slayer( request: HoldSlayerRequest, gateway: AIPSTNGateway = Depends(get_gateway), ): """ 🗡️ The Hold Slayer endpoint. Give it a number and intent, it calls, navigates the IVR, waits on hold, and rings you when a human picks up. Example: POST /api/calls/hold-slayer { "number": "+18005551234", "intent": "cancel my credit card", "call_flow_id": "chase_bank_main", "transfer_to": "sip_phone", "notify": ["sms", "push"] } """ try: call = await gateway.make_call( number=request.number, mode=CallMode.HOLD_SLAYER, intent=request.intent, call_flow_id=request.call_flow_id, device=request.transfer_to, ) return CallResponse( call_id=call.id, status="navigating_ivr", number=request.number, mode="hold_slayer", message="Hold Slayer activated. I'll ring you when a human picks up. ☕", ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/active") async def list_active_calls( gateway: AIPSTNGateway = Depends(get_gateway), ): """List all active calls with their current status.""" calls = gateway.call_manager.active_calls return [call.summary() for call in calls.values()] @router.get("/{call_id}", response_model=CallStatusResponse) async def get_call( call_id: str, gateway: AIPSTNGateway = Depends(get_gateway), ): """Get current call status, transcript so far, classification history.""" call = gateway.get_call(call_id) if not call: raise HTTPException(status_code=404, detail=f"Call {call_id} not found") return CallStatusResponse( call_id=call.id, status=call.status.value, direction=call.direction, remote_number=call.remote_number, mode=call.mode.value, duration=call.duration, hold_time=call.hold_time, audio_type=call.current_classification.value, intent=call.intent, transcript_excerpt=call.transcript[-500:] if call.transcript else None, classification_history=call.classification_history[-50:], current_step=call.current_step_id, services=call.services, ) @router.post("/{call_id}/transfer") async def transfer_call( call_id: str, request: TransferRequest, gateway: AIPSTNGateway = Depends(get_gateway), ): """Transfer an active call to a device.""" try: await gateway.transfer_call(call_id, request.device) return {"status": "transferred", "target": request.device} except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/{call_id}/hangup") async def hangup_call( call_id: str, gateway: AIPSTNGateway = Depends(get_gateway), ): """Hang up a call.""" try: await gateway.hangup_call(call_id) return {"status": "hung_up", "call_id": call_id} except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/{call_id}/dtmf") async def send_dtmf( call_id: str, digits: str, gateway: AIPSTNGateway = Depends(get_gateway), ): """Send DTMF tones on an active call.""" call = gateway.get_call(call_id) if not call: raise HTTPException(status_code=404, detail=f"Call {call_id} not found") # Find the PSTN leg for this call for leg_id, cid in gateway.call_manager._call_legs.items(): if cid == call_id: await gateway.sip_engine.send_dtmf(leg_id, digits) return {"status": "sent", "digits": digits} raise HTTPException(status_code=500, detail="No active SIP leg found for this call")