chore: local testing retell (#23577)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Udit Takkar
2025-09-06 02:11:21 +05:30
committed by GitHub
co-authored by Peer Richelsen
parent 0233c3a11a
commit c42e803cfa
4 changed files with 63 additions and 13 deletions
+5
View File
@@ -386,6 +386,11 @@ UNKEY_ROOT_KEY=
# Used for Cal.ai Voice AI Agents
# https://retellai.com
RETELL_AI_KEY=
# Local testing overrides for Retell AI
RETELL_AI_TEST_MODE=false
# JSON mapping of local to production event type IDs (e.g. {"50": 747280} without any string)
RETELL_AI_TEST_EVENT_TYPE_MAP=
RETELL_AI_TEST_CAL_API_KEY=
# Used for buying phone number for cal ai voice agent
STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID=
@@ -1,5 +1,6 @@
import { v4 as uuidv4 } from "uuid";
import { RETELL_AI_TEST_MODE, RETELL_AI_TEST_EVENT_TYPE_MAP } from "@calcom/lib/constants";
import { timeZoneSchema } from "@calcom/lib/dayjs/timeZone.schema";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
@@ -81,6 +82,13 @@ export class AgentService {
});
}
let eventTypeId = data.eventTypeId;
if (RETELL_AI_TEST_MODE && RETELL_AI_TEST_EVENT_TYPE_MAP) {
const mappedId = RETELL_AI_TEST_EVENT_TYPE_MAP[String(data.eventTypeId)];
eventTypeId = mappedId ? Number(mappedId) : data.eventTypeId;
}
try {
const agent = await this.getAgent(agentId);
const llmId = getLlmId(agent);
@@ -99,8 +107,8 @@ export class AgentService {
const existing = llmDetails?.general_tools ?? [];
const hasCheck = existing.some((t) => t.name === `check_availability_${data.eventTypeId}`);
const hasBook = existing.some((t) => t.name === `book_appointment_${data.eventTypeId}`);
const hasCheck = existing.some((t) => t.name === `check_availability_${eventTypeId}`);
const hasBook = existing.some((t) => t.name === `book_appointment_${eventTypeId}`);
// If both already exist and end_call also exists, nothing to do
const hasEndCallAlready = existing.some((t) => t.type === "end_call");
if (hasCheck && hasBook && hasEndCallAlready) {
@@ -113,27 +121,29 @@ export class AgentService {
)?.cal_api_key;
const apiKey =
reusableKey ??
(await this.createApiKey({
userId: data.userId,
teamId: data.teamId || undefined,
}));
RETELL_AI_TEST_MODE && process.env.RETELL_AI_TEST_CAL_API_KEY
? process.env.RETELL_AI_TEST_CAL_API_KEY
: reusableKey ??
(await this.createApiKey({
userId: data.userId,
teamId: data.teamId || undefined,
}));
const newEventTools: NonNullable<AIPhoneServiceTools<AIPhoneServiceProviderType.RETELL_AI>> = [];
if (!hasCheck) {
newEventTools.push({
name: `check_availability_${data.eventTypeId}`,
name: `check_availability_${eventTypeId}`,
type: "check_availability_cal",
event_type_id: data.eventTypeId,
event_type_id: eventTypeId,
cal_api_key: apiKey,
timezone: data.timeZone,
});
}
if (!hasBook) {
newEventTools.push({
name: `book_appointment_${data.eventTypeId}`,
name: `book_appointment_${eventTypeId}`,
type: "book_appointment_cal",
event_type_id: data.eventTypeId,
event_type_id: eventTypeId,
cal_api_key: apiKey,
timezone: data.timeZone,
});
@@ -180,6 +190,15 @@ export class AgentService {
return;
}
let mappedEventTypeIds = eventTypeIds;
if (RETELL_AI_TEST_MODE && RETELL_AI_TEST_EVENT_TYPE_MAP) {
mappedEventTypeIds = eventTypeIds.map((id) => {
const mappedId = RETELL_AI_TEST_EVENT_TYPE_MAP[String(id)];
return mappedId ? Number(mappedId) : id;
});
}
try {
const agent = await this.getAgent(agentId);
const llmId = getLlmId(agent);
@@ -199,7 +218,7 @@ export class AgentService {
const existing = llmDetails?.general_tools ?? [];
const toolNamesToRemove = eventTypeIds.flatMap((eventTypeId) => [
const toolNamesToRemove = mappedEventTypeIds.flatMap((eventTypeId) => [
`check_availability_${eventTypeId}`,
`book_appointment_${eventTypeId}`,
]);
@@ -212,6 +231,7 @@ export class AgentService {
agentId,
llmId,
removedEventTypes: eventTypeIds,
mappedEventTypes: RETELL_AI_TEST_MODE ? mappedEventTypeIds : undefined,
toolsRemoved: existing.length - filteredTools.length,
});
}
@@ -240,6 +260,15 @@ export class AgentService {
});
}
let mappedActiveEventTypeIds = activeEventTypeIds;
if (RETELL_AI_TEST_MODE && RETELL_AI_TEST_EVENT_TYPE_MAP) {
mappedActiveEventTypeIds = activeEventTypeIds.map((id) => {
const mappedId = RETELL_AI_TEST_EVENT_TYPE_MAP[String(id)];
return mappedId ? Number(mappedId) : id;
});
}
try {
const agent = await this.getAgent(agentId);
const llmId = getLlmId(agent);
@@ -269,7 +298,7 @@ export class AgentService {
if (!eventTypeIdMatch) return false;
const eventTypeId = parseInt(eventTypeIdMatch[1]);
return !activeEventTypeIds.includes(eventTypeId);
return !mappedActiveEventTypeIds.includes(eventTypeId);
});
if (toolsToRemove.length > 0) {
@@ -303,6 +332,7 @@ export class AgentService {
this.logger.error("Failed to cleanup unused tools for agent", {
agentId,
activeEventTypeIds,
mappedActiveEventTypeIds: RETELL_AI_TEST_MODE ? mappedActiveEventTypeIds : undefined,
error,
});
throw new HttpError({
+12
View File
@@ -240,3 +240,15 @@ export const CAL_AI_PHONE_NUMBER_MONTHLY_PRICE = (() => {
const parsed = _rawCalAiPrice && _rawCalAiPrice.trim() !== "" ? Number(_rawCalAiPrice) : NaN;
return Number.isFinite(parsed) ? parsed : 5;
})();
// Retell AI test mode configuration
export const RETELL_AI_TEST_MODE = process.env.RETELL_AI_TEST_MODE === "true";
export const RETELL_AI_TEST_EVENT_TYPE_MAP = (() => {
if (!process.env.RETELL_AI_TEST_EVENT_TYPE_MAP) return null;
try {
return JSON.parse(process.env.RETELL_AI_TEST_EVENT_TYPE_MAP);
} catch (e) {
console.warn("Failed to parse RETELL_AI_TEST_EVENT_TYPE_MAP", e);
return null;
}
})();
+3
View File
@@ -149,6 +149,9 @@
"RENDER_EXTERNAL_URL",
"RESERVED_SUBDOMAINS",
"RETELL_AI_KEY",
"RETELL_AI_TEST_MODE",
"RETELL_AI_TEST_EVENT_TYPE_MAP",
"RETELL_AI_TEST_CAL_API_KEY",
"SALESFORCE_CONSUMER_KEY",
"SALESFORCE_CONSUMER_SECRET",
"SAML_ADMINS",