Files
cal.diy/packages/features/calAIPhone/providers/retellAI/services
d9ebf26e21 chore: standarize rate limit structure (#25736)
* feat: add toggle to opt out of booking title translation in instant meetings (#25547)

* feat: add toggle to opt out of booking title translation in instant meetings

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add autoTranslateTitleEnabled to test builder

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: respect autoTranslateTitleEnabled toggle in InstantBookingCreateService

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: fetch autoTranslateTitleEnabled directly in InstantBookingCreateService

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: rename autoTranslateTitleEnabled to autoTranslateInstantMeetingTitleEnabled

- Renamed field to clarify it only applies to instant meeting title translation
- Moved Prisma query to EventTypeRepository.findInstantMeetingConfigById()
- Removed title translation logic from update.handler.ts (flag only controls instant meeting title)
- Updated all references across the codebase
- Added new i18n translation keys for the renamed field

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add autoTranslateInstantMeetingTitleEnabled to test destructuring patterns

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: use Prisma.TeamCreateInput type for metadata in test helper

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add autoTranslateInstantMeetingTitleEnabled to mockUpdatedEventType in test

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: use generic findByIdMinimal instead of business-specific method

- Add autoTranslateInstantMeetingTitleEnabled to eventTypeSelect constant
- Remove findInstantMeetingConfigById from EventTypeRepository
- Update InstantBookingCreateService to use findByIdMinimal

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: remove autoTranslateInstantMeetingTitleEnabled from eventTypeSelect (not needed for findByIdMinimal)

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* feat: add autoTranslateInstantMeetingTitleEnabled to event type form defaults

- Rename shouldTranslateTitle to shouldAutoTranslateInstantMeetingTitle for clarity
- Add autoTranslateInstantMeetingTitleEnabled to findById and findByIdForOrgAdmin selects
- Add autoTranslateInstantMeetingTitleEnabled to useEventTypeForm defaultValues

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: only update autoTranslateInstantMeetingTitleEnabled when explicitly provided

Fixes issue where omitting the field in update requests would overwrite
the saved opt-out setting with the default value (true). Now the field
is only included in the update payload when explicitly provided.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore: Create standard for rate limits

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-12-10 01:04:21 +00:00
..

RetellAI Services Architecture

This directory contains the refactored RetellAI services that follow the Single Responsibility Principle (SRP) to fix the previous GOD CLASS violation.

Architecture Overview

BEFORE: Single RetellAIService class (849 lines) handling all responsibilities AFTER: Focused services composed together (~150 lines each)

Services

1. AIConfigurationService

Responsibility: LLM and Agent configuration management

  • setupAIConfiguration() - Create LLM and Agent
  • deleteAIConfiguration() - Clean up AI resources
  • updateLLMConfiguration() - Update LLM settings
  • getLLMDetails() - Retrieve LLM information

2. AgentService

Responsibility: Agent CRUD operations

  • createOutboundAgent() - Create new agents
  • updateAgent() - Update agent properties
  • deleteAgent() - Remove agents
  • listAgents() - Get agent lists
  • getAgentWithDetails() - Get detailed agent info

3. BillingService

Responsibility: Stripe payments and subscriptions

  • generatePhoneNumberCheckoutSession() - Create Stripe checkout
  • cancelPhoneNumberSubscription() - Cancel subscriptions

4. CallService

Responsibility: Phone call operations

  • createPhoneCall() - Initiate calls
  • createTestCall() - Create test calls with credit validation

5. PhoneNumberService

Responsibility: Phone number management with transaction integrity

  • importPhoneNumber() - Import with compensating transactions
  • deletePhoneNumber() - Remove phone numbers
  • updatePhoneNumberWithAgents() - Assign agents
  • Includes billing leak protection and compensation failure handling

Usage Examples

Direct Service Usage (Advanced)

import {
  AIConfigurationService,
  AgentService,
  BillingService,
  CallService,
  PhoneNumberService
} from './services';

// Initialize dependencies
const repository = new RetellSDKClient({ apiKey: 'your-key' });
const agentRepository = new PrismaAgentRepositoryAdapter();
const phoneNumberRepository = new PrismaPhoneNumberRepositoryAdapter();
const transactionManager = new PrismaTransactionAdapter();

// Use individual services
const aiConfigService = new AIConfigurationService(repository);
const agentService = new AgentService(repository, agentRepository);
const billingService = new BillingService(phoneNumberRepository, repository);

// Setup AI configuration
const { llmId, agentId } = await aiConfigService.setupAIConfiguration({
  calApiKey: 'cal_live_123...',
  timeZone: 'America/New_York',
  eventTypeId: 12345,
});

// Create agent
const agent = await agentService.createOutboundAgent({
  name: 'Support Agent',
  userId: 1,
  userTimeZone: 'America/New_York',
  setupAIConfiguration: () => aiConfigService.setupAIConfiguration({...})
});
import { RetellAIService } from "../RetellAIService";

// Initialize main service
const service = new RetellAIService(repository, agentRepository, phoneNumberRepository, transactionManager);

// Use composed interface - same as before but internally organized
const { llmId, agentId } = await service.setupAIConfiguration({
  calApiKey: "cal_live_123...",
  timeZone: "America/New_York",
});

const phoneNumber = await service.importPhoneNumber({
  phone_number: "+1234567890",
  termination_uri: "https://example.com/webhook",
  userId: 1,
  agentId: "agent-123",
});

Benefits

✅ Single Responsibility - Each service has one clear purpose
✅ Easier Testing - Test services in isolation with focused mocks
✅ Better Maintainability - Changes to billing don't affect phone operations
✅ Reusability - Compose services differently for different use cases
✅ Code Navigation - ~150 lines per service vs 849 lines
✅ Backward Compatibility - Existing code continues to work

Testing

Each service can be tested independently:

// Test individual service
const aiConfigService = new AIConfigurationService(mockRepository);
await aiConfigService.setupAIConfiguration(config);

// Test main service with all composed services
const service = new RetellAIService(mockRepository, mockAgentRepo, mockPhoneRepo, mockTransaction);

Migration Guide

For new code: Use RetellAIService or individual services for specific needs For existing code: No changes needed - RetellAIService maintains same API

The refactoring eliminates the GOD CLASS antipattern while preserving all functionality and improving code organization.