Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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,
});
Expand Down Expand Up @@ -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);
Expand All @@ -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}`,
]);
Expand All @@ -212,6 +231,7 @@ export class AgentService {
agentId,
llmId,
removedEventTypes: eventTypeIds,
mappedEventTypes: RETELL_AI_TEST_MODE ? mappedEventTypeIds : undefined,
toolsRemoved: existing.length - filteredTools.length,
});
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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({
Expand Down
12 changes: 12 additions & 0 deletions packages/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading