feat: add Ozonetel Set Disposition API for proper ACW release

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 18:35:59 +05:30
parent 72e6373acf
commit 8c6cd2c156
2 changed files with 170 additions and 47 deletions

View File

@@ -8,6 +8,8 @@ export class OzonetelAgentService {
private readonly apiDomain: string;
private readonly apiKey: string;
private readonly accountId: string;
private cachedToken: string | null = null;
private tokenExpiry: number = 0;
constructor(private config: ConfigService) {
this.apiDomain = config.get<string>('exotel.subdomain') ?? 'in1-ccaas-api.ozonetel.com';
@@ -15,6 +17,29 @@ export class OzonetelAgentService {
this.accountId = config.get<string>('exotel.accountSid') ?? '';
}
private async getToken(): Promise<string> {
if (this.cachedToken && Date.now() < this.tokenExpiry) {
return this.cachedToken;
}
const url = `https://${this.apiDomain}/ca_apis/CAToken/generateToken`;
this.logger.log('Generating CloudAgent API token');
const response = await axios.post(url, { userName: this.accountId }, {
headers: { apiKey: this.apiKey, 'Content-Type': 'application/json' },
});
const data = response.data;
if (data.token) {
this.cachedToken = data.token;
this.tokenExpiry = Date.now() + 55 * 60 * 1000;
this.logger.log('CloudAgent token generated successfully');
return data.token;
}
throw new Error(data.message ?? 'Token generation failed');
}
async loginAgent(params: {
agentId: string;
password: string;
@@ -63,61 +88,107 @@ export class OzonetelAgentService {
}
}
async dialCustomer(params: {
async manualDial(params: {
agentId: string;
campaignName: string;
customerNumber: string;
callbackUrl?: string;
ivrUrl?: string;
}): Promise<{ status: string; message: string }> {
const callerId = process.env.OZONETEL_DID ?? '918041763265';
const callbackBase = process.env.KOOKOO_CALLBACK_URL ?? 'https://engage-api.srv1477139.hstgr.cloud';
const roomId = `room-${Date.now()}`;
}): Promise<{ status: string; ucid?: string; message?: string }> {
const url = `https://${this.apiDomain}/ca_apis/AgentManualDial`;
this.logger.log(`Kookoo outbound: dialing ${params.customerNumber}, conference room: ${roomId}`);
this.logger.log(`Manual dial: agent=${params.agentId} campaign=${params.campaignName} number=${params.customerNumber}`);
try {
// Call 1: Call the customer → put in conference room
const customerParams = new URLSearchParams({
phone_no: params.customerNumber,
api_key: this.apiKey,
outbound_version: '2',
caller_id: callerId,
callback_url: params.callbackUrl ?? `${callbackBase}/webhooks/kookoo/callback`,
extra_data: `<response><playtext>Please wait while we connect you to an agent</playtext><conference>${roomId}</conference></response>`,
const token = await this.getToken();
const response = await axios.post(url, {
userName: this.accountId,
agentID: params.agentId,
campaignName: params.campaignName,
customerNumber: params.customerNumber,
UCID: 'true',
}, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
// Call 2: Call the DID (which routes to agent via CloudAgent) → put in same conference room
const agentParams = new URLSearchParams({
phone_no: callerId,
api_key: this.apiKey,
outbound_version: '2',
caller_id: params.customerNumber,
extra_data: `<response><conference>${roomId}</conference></response>`,
});
this.logger.log(`Manual dial response: ${JSON.stringify(response.data)}`);
return response.data;
} catch (error: any) {
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
this.logger.error(`Manual dial failed: ${error.message} ${responseData}`);
throw error;
}
}
// Fire both calls
const [customerRes, agentRes] = await Promise.all([
axios.get(`https://in1-cpaas.ozonetel.com/outbound/outbound.php?${customerParams.toString()}`),
axios.get(`https://in1-cpaas.ozonetel.com/outbound/outbound.php?${agentParams.toString()}`),
]);
async changeAgentState(params: {
agentId: string;
state: 'Ready' | 'Pause';
pauseReason?: string;
}): Promise<{ status: string; message: string }> {
const url = `https://${this.apiDomain}/ca_apis/changeAgentState`;
const responseText = typeof customerRes.data === 'string' ? customerRes.data : String(customerRes.data);
const agentResponseText = typeof agentRes.data === 'string' ? agentRes.data : String(agentRes.data);
this.logger.log(`Kookoo customer dial: ${responseText}`);
this.logger.log(`Kookoo agent dial: ${agentResponseText}`);
this.logger.log(`Changing agent ${params.agentId} state to ${params.state}`);
// Parse XML response: <response><status>queued</status><message>SID</message></response>
const statusMatch = responseText.match(/<status>(.*?)<\/status>/);
const messageMatch = responseText.match(/<message>(.*?)<\/message>/);
const status = statusMatch?.[1] ?? 'unknown';
const message = messageMatch?.[1] ?? responseText;
if (status === 'error') {
throw new Error(message);
try {
const body: Record<string, string> = {
userName: this.accountId,
agentId: params.agentId,
state: params.state,
};
if (params.pauseReason) {
body.pauseReason = params.pauseReason;
}
return { status, message };
const token = await this.getToken();
const response = await axios.post(url, body, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
this.logger.log(`Change agent state response: ${JSON.stringify(response.data)}`);
return response.data;
} catch (error: any) {
this.logger.error(`Kookoo dial failed: ${error.message}`);
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
this.logger.error(`Change agent state failed: ${error.message} ${responseData}`);
throw error;
}
}
async setDisposition(params: {
agentId: string;
ucid: string;
disposition: string;
}): Promise<{ status: string; message?: string; details?: string }> {
const url = `https://${this.apiDomain}/ca_apis/DispositionAPIV2`;
const did = process.env.OZONETEL_DID ?? '918041763265';
this.logger.log(`Set disposition: agent=${params.agentId} ucid=${params.ucid} disposition=${params.disposition}`);
try {
const token = await this.getToken();
const response = await axios.post(url, {
userName: this.accountId,
agentID: params.agentId,
did,
ucid: params.ucid,
action: 'Set',
disposition: params.disposition,
autoRelease: 'true',
}, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
this.logger.log(`Set disposition response: ${JSON.stringify(response.data)}`);
return response.data;
} catch (error: any) {
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
this.logger.error(`Set disposition failed: ${error.message} ${responseData}`);
throw error;
}
}