feat: call control, recording, CDR, missed calls, live call assist

- Call Control API (CONFERENCE/HOLD/MUTE/KICK_CALL)
- Recording pause/unpause
- Fetch CDR Detailed (call history with recordings)
- Abandon Calls (missed calls from Ozonetel)
- Call Assist WebSocket gateway (Deepgram STT + OpenAI suggestions)
- Call Assist service (lead context loading)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-21 10:36:35 +05:30
parent 58225b7943
commit bbf77ed0e9
8 changed files with 511 additions and 1 deletions

View File

@@ -193,6 +193,165 @@ export class OzonetelAgentService {
}
}
async callControl(params: {
action: 'CONFERENCE' | 'HOLD' | 'UNHOLD' | 'MUTE' | 'UNMUTE' | 'KICK_CALL';
ucid: string;
conferenceNumber?: string;
}): Promise<{ status: string; message: string; ucid?: string }> {
const url = `https://${this.apiDomain}/ca_apis/CallControl_V4`;
const did = process.env.OZONETEL_DID ?? '918041763265';
const agentPhoneName = process.env.OZONETEL_SIP_ID ?? '523590';
this.logger.log(`Call control: action=${params.action} ucid=${params.ucid} conference=${params.conferenceNumber ?? 'none'}`);
try {
const token = await this.getToken();
const body: Record<string, string> = {
userName: this.accountId,
action: params.action,
ucid: params.ucid,
did,
agentPhoneName,
};
if (params.conferenceNumber) {
body.conferenceNumber = params.conferenceNumber;
}
const response = await axios.post(url, body, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
this.logger.log(`Call control response: ${JSON.stringify(response.data)}`);
return response.data;
} catch (error: any) {
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
this.logger.error(`Call control failed: ${error.message} ${responseData}`);
throw error;
}
}
async pauseRecording(params: {
ucid: string;
action: 'pause' | 'unPause';
}): Promise<{ status: string; message: string }> {
const url = `https://${this.apiDomain}/CAServices/Call/Record.php`;
this.logger.log(`Recording ${params.action}: ucid=${params.ucid}`);
try {
const response = await axios.get(url, {
params: {
userName: this.accountId,
apiKey: this.apiKey,
action: params.action,
ucid: params.ucid,
},
});
this.logger.log(`Recording control response: ${JSON.stringify(response.data)}`);
return response.data;
} catch (error: any) {
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
this.logger.error(`Recording control failed: ${error.message} ${responseData}`);
throw error;
}
}
async getAbandonCalls(params?: {
fromTime?: string;
toTime?: string;
campaignName?: string;
}): Promise<Array<{
monitorUCID: string;
type: string;
status: string;
campaign: string;
callerID: string;
did: string;
agentID: string;
agent: string;
hangupBy: string;
callTime: string;
}>> {
const url = `https://${this.apiDomain}/ca_apis/abandonCalls`;
this.logger.log('Fetching abandon calls');
try {
const token = await this.getToken();
const body: Record<string, string> = { userName: this.accountId };
if (params?.fromTime) body.fromTime = params.fromTime;
if (params?.toTime) body.toTime = params.toTime;
if (params?.campaignName) body.campaignName = params.campaignName;
const response = await axios({
method: 'GET',
url,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: JSON.stringify(body),
});
const data = response.data;
this.logger.log(`Abandon calls response: ${JSON.stringify(data).substring(0, 200)}`);
if (data.status === 'success' && Array.isArray(data.message)) {
return data.message;
}
return [];
} catch (error: any) {
this.logger.error(`Abandon calls failed: ${error.message}`);
return [];
}
}
async fetchCDR(params: {
date: string; // YYYY-MM-DD
campaignName?: string;
status?: string;
callType?: string;
}): Promise<Array<Record<string, any>>> {
const url = `https://${this.apiDomain}/ca_reports/fetchCDRDetails`;
this.logger.log(`Fetch CDR: date=${params.date}`);
try {
const token = await this.getToken();
const body: Record<string, string> = {
userName: this.accountId,
fromDate: `${params.date} 00:00:00`,
toDate: `${params.date} 23:59:59`,
};
if (params.campaignName) body.campaignName = params.campaignName;
if (params.status) body.status = params.status;
if (params.callType) body.callType = params.callType;
const response = await axios({
method: 'GET',
url,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: JSON.stringify(body),
});
const data = response.data;
if (data.status === 'success' && Array.isArray(data.details)) {
return data.details;
}
return [];
} catch (error: any) {
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
this.logger.error(`Fetch CDR failed: ${error.message} ${responseData}`);
return [];
}
}
async logoutAgent(params: {
agentId: string;
password: string;