import {z} from 'zod' export type SummaryEnumProcessingState = 'complete' | 'partial'; export type GetApiV1VoiceCallSidSummaryArgs = {CallSid: string, ProcessingState?: SummaryEnumProcessingState | undefined}; export type GetApiV1VoiceCallSidSummaryOptions = {baseUrl?: string | undefined, accountSidAuthToken?: {username: string, password: string} | undefined, fetch?: typeof fetch | undefined}; export const summaryEnumCallType = z.enum(["carrier", "sip", "trunking", "client", "whatsapp"]); export const summaryEnumCallState = z.enum(["ringing", "completed", "busy", "fail", "noanswer", "canceled", "answered", "undialed"]); export const summaryEnumAnsweredBy = z.enum(["unknown", "machine_start", "machine_end_beep", "machine_end_silence", "machine_end_other", "human", "fax"]); export const summaryEnumProcessingState = z.enum(["complete", "partial"]); export const callSummaryCrelayRateStats = z.object({min: z.number().optional(), max: z.number().optional(), avg: z.number().optional()}); export const callSummaryCrelayTokenStats = z.object({total: z.number().int().optional(), tokens_per_second: callSummaryCrelayRateStats.optional()}); export const callSummaryCrelayWordStats = z.object({total: z.number().int().optional(), words_per_minute: callSummaryCrelayRateStats.optional()}); export const callSummaryCrelayInterruptions = z.object({customer_to_agent: z.number().int().optional(), agent_to_customer: z.number().int().optional()}); export const callSummaryCrelaySessionState = z.enum(["unknown", "failure", "ended", "hung_up"]); export const callSummaryAgentSessionSummary = z.object({session_id: z.string().optional(), tts_latency_ms: callSummaryCrelayRateStats.optional(), stt_latency_ms: callSummaryCrelayRateStats.optional(), network_latency_ms: callSummaryCrelayRateStats.optional(), time_to_first_audio_ms: callSummaryCrelayRateStats.optional(), application_latency_ms: callSummaryCrelayRateStats.optional(), tokens: callSummaryCrelayTokenStats.optional(), words: callSummaryCrelayWordStats.optional(), turns: z.number().int().optional(), interruptions: callSummaryCrelayInterruptions.optional(), session_state: callSummaryCrelaySessionState.optional()}); export const insightsV1CallSummary = z.object({account_sid: z.string().min(34).max(34).nullable().optional(), call_sid: z.string().min(34).max(34).nullable().optional(), call_type: summaryEnumCallType.optional(), call_state: summaryEnumCallState.optional(), answered_by: summaryEnumAnsweredBy.optional(), processing_state: summaryEnumProcessingState.optional(), created_time: z.string().nullable().optional(), start_time: z.string().nullable().optional(), end_time: z.string().nullable().optional(), duration: z.number().int().nullable().optional(), connect_duration: z.number().int().nullable().optional(), from: z.unknown(), to: z.unknown(), carrier_edge: z.unknown(), client_edge: z.unknown(), sdk_edge: z.unknown(), sip_edge: z.unknown(), tags: z.array(z.string()).nullable().optional(), url: z.string().nullable().optional(), attributes: z.unknown(), properties: z.unknown(), trust: z.unknown(), annotation: z.unknown(), agent_session_summaries: z.array(callSummaryAgentSessionSummary).nullable().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Get a specific Call Summary. * * GET /v1/Voice/{CallSid}/Summary */ export const getApiV1VoiceCallSidSummary = async (args: GetApiV1VoiceCallSidSummaryArgs, options?: GetApiV1VoiceCallSidSummaryOptions) => { const {baseUrl = "https://insights.twilio.com", fetch: fetchFn = fetch, accountSidAuthToken} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.ProcessingState != null) query.append("ProcessingState", String(args.ProcessingState)) if (accountSidAuthToken !== undefined) headers.set('authorization', `Basic ${btoa(`${accountSidAuthToken.username}:${accountSidAuthToken.password}`)}`) const url = `${baseUrl.replace(/\/+$/, '')}/v1/Voice/${encodeURIComponent(String(args.CallSid))}/Summary${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(insightsV1CallSummary, await res.json()) };