Skip to content

Commit 5c70060

Browse files
authored
feat: voice and language support in cal.ai (calcom#23865)
* feat: lang support * fix: type errors * feat: select voice agent * refactor: address feedback * refactor: address feedback * refactor: missing import * fix: types
1 parent ae7f8f1 commit 5c70060

20 files changed

Lines changed: 563 additions & 8 deletions

File tree

apps/web/public/icons/sprite.svg

Lines changed: 7 additions & 0 deletions
Loading

apps/web/public/static/locales/en/common.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3704,5 +3704,13 @@
37043704
"start_from_scratch_description": "Create your own workflow from scratch.",
37053705
"cal_ai_template_title": "Cal.ai template",
37063706
"cal_ai_template_description": "AI agents that book meetings, send reminders, and follow up!",
3707+
"voice": "Voice",
3708+
"select_voice": "Select Voice",
3709+
"select_voice_for_agent": "Select a voice for your agent",
3710+
"choose_a_voice_for_your_agent": "Choose a voice for your agent",
3711+
"trait": "Trait",
3712+
"voice_id": "Voice ID",
3713+
"use_voice": "Use Voice",
3714+
"current_voice": "Current Voice",
37073715
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
37083716
}

packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ export type AIPhoneServiceListCallsResponse<
9696
T extends AIPhoneServiceProviderType = AIPhoneServiceProviderType
9797
> = AIPhoneServiceProviderTypeMap[T]["ListCallsResponse"];
9898

99+
export type AIPhoneServiceVoice<T extends AIPhoneServiceProviderType = AIPhoneServiceProviderType> =
100+
AIPhoneServiceProviderTypeMap[T]["Voice"];
101+
99102
export interface AIPhoneServiceDeletion {
100103
modelId?: string;
101104
agentId?: string;
@@ -311,6 +314,7 @@ export interface AIPhoneServiceProvider<T extends AIPhoneServiceProviderType = A
311314
beginMessage?: string | null;
312315
generalTools?: AIPhoneServiceTools<T>;
313316
voiceId?: string;
317+
language?: string;
314318
}): Promise<{ message: string }>;
315319

316320
/**
@@ -374,6 +378,11 @@ export interface AIPhoneServiceProvider<T extends AIPhoneServiceProviderType = A
374378
startTimestamp?: { lower_threshold?: number; upper_threshold?: number };
375379
};
376380
}): Promise<AIPhoneServiceListCallsResponse<T>>;
381+
382+
/**
383+
* List available voices
384+
*/
385+
listVoices(): Promise<AIPhoneServiceVoice<T>[]>;
377386
}
378387

379388
/**

packages/features/calAIPhone/providers/retellAI/RetellAIPhoneServiceProvider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type { AgentRepositoryInterface } from "../interfaces/AgentRepositoryInte
2222
import type { PhoneNumberRepositoryInterface } from "../interfaces/PhoneNumberRepositoryInterface";
2323
import type { TransactionInterface } from "../interfaces/TransactionInterface";
2424
import { RetellAIService } from "./RetellAIService";
25-
import type { RetellAIRepository } from "./types";
25+
import type { RetellAIRepository, Language } from "./types";
2626

2727
export class RetellAIPhoneServiceProvider
2828
implements AIPhoneServiceProvider<AIPhoneServiceProviderType.RETELL_AI>
@@ -243,6 +243,7 @@ export class RetellAIPhoneServiceProvider
243243
beginMessage?: string | null;
244244
generalTools?: AIPhoneServiceTools<AIPhoneServiceProviderType.RETELL_AI>;
245245
voiceId?: string;
246+
language?: Language;
246247
}): Promise<{ message: string }> {
247248
return await this.service.updateAgentConfiguration(params);
248249
}
@@ -302,4 +303,8 @@ export class RetellAIPhoneServiceProvider
302303
}) {
303304
return await this.service.listCalls(params);
304305
}
306+
307+
async listVoices() {
308+
return await this.service.listVoices();
309+
}
305310
}

packages/features/calAIPhone/providers/retellAI/RetellAIService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AgentService } from "./services/AgentService";
1212
import { BillingService } from "./services/BillingService";
1313
import { CallService } from "./services/CallService";
1414
import { PhoneNumberService } from "./services/PhoneNumberService";
15+
import { VoiceService } from "./services/VoiceService";
1516
import type {
1617
RetellLLM,
1718
RetellCall,
@@ -32,6 +33,7 @@ export class RetellAIService {
3233
private billingService: BillingService;
3334
private callService: CallService;
3435
private phoneNumberService: PhoneNumberService;
36+
private voiceService: VoiceService;
3537

3638
constructor(
3739
private repository: RetellAIRepository,
@@ -49,6 +51,7 @@ export class RetellAIService {
4951
phoneNumberRepository,
5052
transactionManager
5153
);
54+
this.voiceService = new VoiceService(repository);
5255

5356
// Inject RetellAIService reference into CallService
5457
this.callService.setRetellAIService(this);
@@ -171,6 +174,7 @@ export class RetellAIService {
171174
beginMessage?: string | null;
172175
generalTools?: RetellLLMGeneralTools;
173176
voiceId?: string;
177+
language?: Language;
174178
}) {
175179
return this.agentService.updateAgentConfiguration({
176180
...params,
@@ -254,4 +258,8 @@ export class RetellAIService {
254258
}) {
255259
return this.callService.listCalls(params);
256260
}
261+
262+
async listVoices() {
263+
return this.voiceService.listVoices();
264+
}
257265
}

packages/features/calAIPhone/providers/retellAI/RetellSDKClient.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
ImportPhoneNumberParams,
1717
RetellCallListParams,
1818
RetellCallListResponse,
19+
RetellVoice,
1920
} from "./types";
2021

2122
const RETELL_API_KEY = process.env.RETELL_AI_KEY;
@@ -270,4 +271,16 @@ export class RetellSDKClient implements RetellAIRepository {
270271
throw error;
271272
}
272273
}
274+
275+
async listVoices(): Promise<RetellVoice[]> {
276+
277+
try {
278+
const response = await this.client.voice.list();
279+
280+
return response;
281+
} catch (error) {
282+
this.logger.error("Failed to list voices", { error });
283+
throw error;
284+
}
285+
}
273286
}

packages/features/calAIPhone/providers/retellAI/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
RetellDynamicVariables,
2222
RetellCallListParams,
2323
RetellCallListResponse,
24+
RetellVoice,
2425
} from "./types";
2526

2627
export { RetellAIService } from "./RetellAIService";
@@ -30,6 +31,7 @@ export { AgentService } from "./services/AgentService";
3031
export { BillingService } from "./services/BillingService";
3132
export { CallService } from "./services/CallService";
3233
export { PhoneNumberService } from "./services/PhoneNumberService";
34+
export { VoiceService } from "./services/VoiceService";
3335

3436
export { RetellAIPhoneServiceProvider } from "./RetellAIPhoneServiceProvider";
3537
export { RetellAIPhoneServiceProviderFactory } from "./RetellAIPhoneServiceProviderFactory";
@@ -65,6 +67,7 @@ export type {
6567
RetellDynamicVariables,
6668
RetellCallListParams,
6769
RetellCallListResponse,
70+
RetellVoice,
6871
};
6972

7073
export interface RetellAIPhoneServiceProviderTypeMap {
@@ -83,6 +86,7 @@ export interface RetellAIPhoneServiceProviderTypeMap {
8386
AgentWithDetails: RetellAgentWithDetails;
8487
ListCallsParams: RetellCallListParams;
8588
ListCallsResponse: RetellCallListResponse;
89+
Voice: RetellVoice;
8690
}
8791

8892
// ===== USAGE EXAMPLES =====

packages/features/calAIPhone/providers/retellAI/services/AgentService.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ export class AgentService {
507507
beginMessage,
508508
generalTools,
509509
voiceId,
510+
language,
510511
updateLLMConfiguration,
511512
}: {
512513
id: string;
@@ -517,6 +518,7 @@ export class AgentService {
517518
beginMessage?: string | null;
518519
generalTools?: AIPhoneServiceTools<AIPhoneServiceProviderType.RETELL_AI>;
519520
voiceId?: string;
521+
language?: Language;
520522
updateLLMConfiguration: (
521523
llmId: string,
522524
data: AIPhoneServiceUpdateModelParams<AIPhoneServiceProviderType.RETELL_AI>
@@ -539,7 +541,8 @@ export class AgentService {
539541
generalPrompt !== undefined ||
540542
beginMessage !== undefined ||
541543
generalTools !== undefined ||
542-
voiceId !== undefined;
544+
voiceId !== undefined ||
545+
language !== undefined;
543546

544547
if (hasRetellUpdates) {
545548
try {
@@ -558,10 +561,11 @@ export class AgentService {
558561
await updateLLMConfiguration(llmId, llmUpdateData);
559562
}
560563

561-
if (voiceId) {
562-
await this.updateAgent(agent.providerAgentId, {
563-
voice_id: voiceId,
564-
});
564+
if (voiceId || language) {
565+
const agentUpdateData: Parameters<typeof this.updateAgent>[1] = {};
566+
if (voiceId) agentUpdateData.voice_id = voiceId;
567+
if (language) agentUpdateData.language = language;
568+
await this.updateAgent(agent.providerAgentId, agentUpdateData);
565569
}
566570
} catch (error) {
567571
this.logger.error("Failed to update agent configuration in external AI service", {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { HttpError } from "@calcom/lib/http-error";
2+
import logger from "@calcom/lib/logger";
3+
4+
import type { RetellAIRepository, RetellVoice } from "../types";
5+
6+
export class VoiceService {
7+
private logger = logger.getSubLogger({ prefix: ["VoiceService"] });
8+
9+
constructor(private retellRepository: RetellAIRepository) {}
10+
11+
async listVoices(): Promise<RetellVoice[]> {
12+
try {
13+
const voices = await this.retellRepository.listVoices();
14+
15+
this.logger.info("Retrieved voices successfully", {
16+
count: voices.length,
17+
});
18+
19+
return voices;
20+
} catch (error) {
21+
this.logger.error("Failed to list voices from external AI service", {
22+
error,
23+
});
24+
throw new HttpError({
25+
statusCode: 500,
26+
message: "Failed to retrieve available voices. Please try again.",
27+
});
28+
}
29+
}
30+
}

packages/features/calAIPhone/providers/retellAI/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type RetellLLM = Retell.LlmResponse;
66
export type RetellPhoneNumber = Retell.PhoneNumberResponse;
77
export type RetellCall = Retell.PhoneCallResponse;
88
export type RetellDynamicVariables = { [key: string]: unknown };
9+
export type RetellVoice = Retell.VoiceResponse;
910

1011
export type RetellAgent = Retell.AgentResponse;
1112

@@ -186,4 +187,7 @@ export interface RetellAIRepository {
186187
createWebCall(
187188
data: CreateWebCallParams
188189
): Promise<{ call_id: string; access_token: string; agent_id: string }>;
190+
191+
// Voice operations
192+
listVoices(): Promise<RetellVoice[]>;
189193
}

0 commit comments

Comments
 (0)