* feat: cal ai self serve architecture * chore: add package * chore: update evnet controller * refactor: improvements * chore: rename * chore: type error and naming * chore: just set it to nul * chore: just set it to nul * chore: some more improvements * chore: packate version * fix: API v2 * chore: change name of files * chore: add select * chore: add missing teamId * chore: save progress * refactor: split into multiple services * refactor: make schema provider agonistic * chore: improvements * chore: * chore: remove duplicate files * chore: semicolon * chore: formatting * refactor: logging and error handling * chore: rename variable * refactor: use trpc error * chore: replace with HttpError * chore: remove from option * We need the enum and not just the type --------- Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
import { PROMPT_TEMPLATES } from "@calcom/features/calAIPhone/promptTemplates";
|
|
import { RetellAIService, validatePhoneNumber } from "@calcom/features/calAIPhone/retellAIService";
|
|
import { templateTypeEnum } from "@calcom/features/calAIPhone/zod-utils";
|
|
import type { TCreatePhoneCallSchema } from "@calcom/features/calAIPhone/zod-utils";
|
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
|
import logger from "@calcom/lib/logger";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
export const handleCreatePhoneCall = async ({
|
|
user,
|
|
input,
|
|
}: {
|
|
user: { timeZone: string; id: number; profile?: { organization?: { id?: number } } };
|
|
input: TCreatePhoneCallSchema;
|
|
}) => {
|
|
if (!user?.profile?.organization) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "User is not part of an organization" });
|
|
}
|
|
|
|
await checkRateLimitAndThrowError({
|
|
rateLimitingType: "core",
|
|
identifier: `createPhoneCall:${user.id}`,
|
|
});
|
|
|
|
await validatePhoneNumber(input.yourPhoneNumber);
|
|
|
|
const {
|
|
yourPhoneNumber,
|
|
numberToCall,
|
|
guestName,
|
|
guestEmail,
|
|
guestCompany,
|
|
eventTypeId,
|
|
beginMessage,
|
|
calApiKey,
|
|
templateType,
|
|
schedulerName,
|
|
generalPrompt: userCustomPrompt,
|
|
} = input;
|
|
|
|
const generalPrompt =
|
|
templateType === templateTypeEnum.enum.CUSTOM_TEMPLATE
|
|
? userCustomPrompt
|
|
: PROMPT_TEMPLATES[templateType]?.generalPrompt;
|
|
|
|
const retellAI = new RetellAIService({
|
|
templateType,
|
|
generalPrompt: generalPrompt ?? "",
|
|
beginMessage: beginMessage ?? null,
|
|
yourPhoneNumber,
|
|
loggedInUserTimeZone: user.timeZone,
|
|
eventTypeId,
|
|
calApiKey,
|
|
dynamicVariables: {
|
|
guestName,
|
|
guestEmail,
|
|
guestCompany,
|
|
schedulerName,
|
|
},
|
|
});
|
|
|
|
const aiPhoneCallConfig = await prisma.aIPhoneCallConfiguration.upsert({
|
|
where: {
|
|
eventTypeId,
|
|
},
|
|
update: {
|
|
beginMessage,
|
|
enabled: true,
|
|
guestName,
|
|
guestEmail,
|
|
guestCompany,
|
|
numberToCall,
|
|
yourPhoneNumber,
|
|
schedulerName,
|
|
templateType,
|
|
generalPrompt,
|
|
},
|
|
create: {
|
|
eventTypeId,
|
|
beginMessage,
|
|
enabled: true,
|
|
guestName,
|
|
guestEmail,
|
|
guestCompany,
|
|
numberToCall,
|
|
yourPhoneNumber,
|
|
schedulerName,
|
|
templateType,
|
|
generalPrompt,
|
|
},
|
|
});
|
|
|
|
// If no retell LLM is associated with the event type, create one
|
|
if (!aiPhoneCallConfig.llmId) {
|
|
const createdRetellLLM = await retellAI.createRetellLLMAndUpdateWebsocketUrl();
|
|
|
|
await prisma.aIPhoneCallConfiguration.update({
|
|
where: {
|
|
eventTypeId,
|
|
},
|
|
data: {
|
|
llmId: createdRetellLLM.llm_id,
|
|
},
|
|
});
|
|
} else {
|
|
// aiPhoneCallConfig.llmId would be set here in the else block
|
|
const retellLLM = await retellAI.getRetellLLM(aiPhoneCallConfig.llmId as string);
|
|
|
|
const shouldUpdateLLM =
|
|
retellLLM.general_prompt !== generalPrompt || retellLLM.begin_message !== beginMessage;
|
|
|
|
if (shouldUpdateLLM) {
|
|
const updatedRetellLLM = await retellAI.updatedRetellLLMAndUpdateWebsocketUrl(
|
|
aiPhoneCallConfig.llmId as string
|
|
);
|
|
logger.debug("updated Retell LLM", updatedRetellLLM);
|
|
}
|
|
}
|
|
|
|
const createPhoneCallRes = await retellAI.createRetellPhoneCall(numberToCall);
|
|
logger.debug("Create Call Response", createPhoneCallRes);
|
|
|
|
return { callId: createPhoneCallRes.call_id, agentId: createPhoneCallRes.agent_id };
|
|
};
|