-
Notifications
You must be signed in to change notification settings - Fork 10.2k
feat: cal.ai self serve #21827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: cal.ai self serve #21827
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 2 Skipped Deployments
|
✅ No security or compliance issues detected. Reviewed everything up to b1b9aac. Security Overview
Detected Code ChangesThe diff is too large to display a summary of code changes. Reply to this PR with |
makeSelfServePhoneCall: authedProcedure | ||
.input(z.object({ eventTypeId: z.number(), numberToCall: z.string() })) | ||
.mutation(async ({ ctx, input }) => { | ||
const { eventTypeId, numberToCall } = input; | ||
console.log("makeSelfServePhoneCall", ctx, input); | ||
|
||
const { handleCreateSelfServePhoneCall } = await import( | ||
"@calcom/features/ee/cal-ai-phone/handleCreateSelfServePhoneCall" | ||
); | ||
const call = await handleCreateSelfServePhoneCall({ | ||
userId: ctx.user.id, | ||
eventTypeId, | ||
numberToCall, | ||
}); | ||
return call; | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Endpoint for making phone call
}); | ||
utils.viewer.workflows.getVerifiedNumbers.invalidate(); | ||
} | ||
await validateAndSubmitWorkflow(values); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No change in logic just moved to a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (9)
packages/features/ee/workflows/components/AgentConfigurationSheet.tsx (3)
95-910
: Consider component decomposition for better maintainability.This component is over 900 lines and handles multiple distinct responsibilities:
- Agent prompt configuration (lines 391-507)
- Phone number management and display (lines 509-644)
- Multiple dialog components (lines 661-898)
While the current structure is functional, consider extracting smaller, focused components as suggested in previous reviews:
The component size concern remains valid and would benefit from decomposition into smaller, more focused components.
691-691
: Improve dialog header subtitle for better user experience.The import dialog subtitle currently uses a generic description. Consider using a more specific localization key:
- <DialogHeader title={t("import_phone_number")} subtitle={t("import_phone_number_description")} />
This addresses the same concern raised in previous reviews about using more descriptive subtitle text.
72-81
: Remove extensive commented code blocks to improve maintainability.The file contains over 400 lines of commented code related to tool functionality. This significantly impacts readability and maintainability. Since this is planned for V2 according to the developer's comment, consider:
- Remove all commented code blocks
- Create GitHub issues to track the planned V2 features
- Use version control history to preserve the code if needed later
This addresses the same concern raised in previous reviews about excessive commented code making the component harder to maintain.
Also applies to: 116-118, 263-315, 433-505, 900-1027
packages/features/ee/workflows/components/WorkflowStepContainer.tsx (6)
641-641
: Missing localization for "Cal.ai Agent" text.The hardcoded text should use the translation function per coding guidelines.
-<h2 className="text-emphasis text-sm font-medium leading-none">Cal.ai Agent</h2> +<h2 className="text-emphasis text-sm font-medium leading-none">{t("cal_ai_agent")}</h2>
648-675
: Investigate root cause of action reset workaround.The post-save logic (lines 659-664) manually resets the action field, which indicates an underlying state management issue in the
onSaveWorkflow
implementation. This workaround masks the real problem.Please investigate why
onSaveWorkflow
is overwriting the form's action field and fix the root cause rather than applying this patch.
689-689
: Missing localization for "Cal.ai Agent" text.-<h3 className="text-emphasis text-base font-medium">Cal.ai Agent</h3> +<h3 className="text-emphasis text-base font-medium">{t("cal_ai_agent")}</h3>
704-704
: Missing localization for hardcoded text.-<span className="text-subtle text-sm">{t("No phone number connected")}</span> +<span className="text-subtle text-sm">{t("no_phone_number_connected")}</span>
1369-1404
: Several text strings still need localization.Multiple hardcoded strings should use the
t()
function:-<DialogContent type="creation" title={t("unsubscribe_phone_number")}> - <p className="text-default text-sm">{t("are_you_still_want_to_unsubscribe")}</p> - <p className="text-subtle text-sm">{t("the_action_will_disconnect_phone_number")}</p> - <Button type="button" color="secondary" onClick={() => setIsUnsubscribeDialogOpen(false)}> - {t("cancel")} - </Button> - {t("unsubscribe")}
1412-1484
: Multiple hardcoded strings need localization in delete dialog.Several text strings should use the translation function:
-<DialogContent type="confirmation" title={t("delete_workflow_step")}> - <p className="text-default text-sm">{t("are_you_sure_you_want_to_delete_workflow_step")}</p> - <p className="text-attention text-sm font-medium">{t("this_action_will_also")}</p> - <li>{t("cancel_your_phone_number_subscription")}</li> - <li>{t("delete_associated_phone_number")}</li> - {t("active_subscription")} - {t("Cancel")} - {t("Delete")}
🧹 Nitpick comments (3)
apps/web/public/static/locales/en/common.json (1)
20-37
: Minor wording inconsistency – “User Name” vs “Username”
"sip_trunk_username": "SIP Trunk User Name"
is the only key in the file that splits “username” into two words. Elsewhere (e.g."username"
at Line 321) we use the single-word form. Consider normalising to “SIP Trunk Username” for consistency.packages/features/ee/workflows/components/AgentConfigurationSheet.tsx (1)
856-856
: Fix inconsistent localization key format.The Cancel button uses a hardcoded string instead of following the consistent lowercase format used elsewhere:
- <Button onClick={() => setIsImportDialogOpen(false)} color="secondary"> - {t("Cancel")} + <Button onClick={() => setIsImportDialogOpen(false)} color="secondary"> + {t("cancel")}This ensures consistency with the localization key format used throughout the component (e.g., line 648:
t("cancel")
).docs/ai-voice-call-credits.md (1)
5-5
: Fix grammatical error in overview.The sentence contains redundant wording that affects clarity:
-This document describes the credit system for AI voice calls in Cal.com. The system required with a **minimum requirement of 5 credits** to initiate any phone call. +This document describes the credit system for AI voice calls in Cal.com. The system requires a **minimum of 5 credits** to initiate any phone call.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.env.example
(1 hunks)apps/web/public/static/locales/en/common.json
(14 hunks)docs/ai-voice-call-credits.md
(1 hunks)packages/features/ee/workflows/components/AgentConfigurationSheet.tsx
(1 hunks)packages/features/ee/workflows/components/TestAgentDialog.tsx
(1 hunks)packages/features/ee/workflows/components/WorkflowStepContainer.tsx
(14 hunks)packages/features/ee/workflows/lib/reminders/aiPhoneCallManager.ts
(1 hunks)packages/features/ee/workflows/pages/workflow.tsx
(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- .env.example
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/features/ee/workflows/pages/workflow.tsx
- packages/features/ee/workflows/components/TestAgentDialog.tsx
- packages/features/ee/workflows/lib/reminders/aiPhoneCallManager.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)
Always use
t()
for text localization in frontend code; direct text embedding should trigger a warning
Files:
packages/features/ee/workflows/components/AgentConfigurationSheet.tsx
packages/features/ee/workflows/components/WorkflowStepContainer.tsx
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()
in hot paths like loops
Files:
packages/features/ee/workflows/components/AgentConfigurationSheet.tsx
packages/features/ee/workflows/components/WorkflowStepContainer.tsx
🧠 Learnings (5)
📓 Common learnings
Learnt from: Udit-takkar
PR: calcom/cal.com#21827
File: packages/lib/server/repository/phoneNumber.ts:153-160
Timestamp: 2025-08-05T13:17:23.491Z
Learning: In the Cal.com Cal AI phone feature, the deletePhoneNumber repository method validation is properly handled in the service layer (RetellAIService.deletePhoneNumber) which validates user ownership and authorization before calling the repository method. The repository layer correctly focuses on data access only.
📚 Learning: applies to **/*.tsx : always use `t()` for text localization in frontend code; direct text embedding...
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.tsx : Always use `t()` for text localization in frontend code; direct text embedding should trigger a warning
Applied to files:
packages/features/ee/workflows/components/AgentConfigurationSheet.tsx
packages/features/ee/workflows/components/WorkflowStepContainer.tsx
📚 Learning: when making localization changes for new features, it's often safer to add new strings rather than m...
Learnt from: bandhan-majumder
PR: calcom/cal.com#22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.
Applied to files:
packages/features/ee/workflows/components/AgentConfigurationSheet.tsx
packages/features/ee/workflows/components/WorkflowStepContainer.tsx
apps/web/public/static/locales/en/common.json
📚 Learning: in signup-view.tsx, when checking if redirecturl contains certain strings, using explicit && checks ...
Learnt from: Anshumancanrock
PR: calcom/cal.com#22570
File: apps/web/modules/signup-view.tsx:253-253
Timestamp: 2025-07-21T21:33:23.371Z
Learning: In signup-view.tsx, when checking if redirectUrl contains certain strings, using explicit && checks (redirectUrl && redirectUrl.includes()) is preferred over optional chaining (redirectUrl?.includes()) to ensure the result is always a boolean rather than potentially undefined. This approach provides cleaner boolean contracts for downstream conditional logic.
Applied to files:
packages/features/ee/workflows/components/WorkflowStepContainer.tsx
📚 Learning: in the failedbookingsbyfield component (packages/features/insights/components/failedbookingsbyfield....
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.
Applied to files:
packages/features/ee/workflows/components/WorkflowStepContainer.tsx
🪛 Biome (2.1.2)
apps/web/public/static/locales/en/common.json
[error] 3419-3419: The key timezone_mismatch_tooltip was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (20)
apps/web/public/static/locales/en/common.json (9)
45-50
: LGTM – new agent/phone number status stringsThe added success & validation messages follow the existing naming convention and grammar.
153-155
: LGTM – prompt-related stringsKeys and descriptions are clear and consistent with neighbouring entries.
176-178
: LGTM – test-call copyNo issues found; wording is concise and self-explanatory.
303-307
: LGTM – initial-message copyKeys, descriptions and example value read well.
325-328
: LGTM – help/learn stringsWording is fine and matches existing tone.
631-636
: LGTM – subscription-cancellation & generic updating stringsNo inconsistencies spotted.
712-713
: LGTM – test-call phone-number promptString is clear and matches UI usage.
1268-1270
: LGTM – “Make Test Call” CTANo issues.
1772-1790
: LGTM – Cal AI phone-number management stringsKeys are grouped logically; spelling and punctuation look correct.
packages/features/ee/workflows/components/AgentConfigurationSheet.tsx (1)
170-170
: Fix localization for success messages.Several success messages are properly localized, but ensure consistency across all toast messages. These appear to be correctly using the
t()
function already:
- Line 170:
t("phone_number_purchased_successfully")
✓- Line 187:
t("phone_number_imported_successfully")
✓- Line 203:
t("phone_number_subscription_cancelled_successfully")
✓- Line 219:
t("phone_number_deleted_successfully")
✓The localization has been properly implemented for these success messages.
Also applies to: 187-187, 203-203, 219-219
docs/ai-voice-call-credits.md (5)
21-52
: Excellent integration documentation.This section provides comprehensive and practical guidance for integrating with Retell AI, including:
- Clear setup instructions with specific dashboard navigation
- Well-defined credit deduction logic
- Sensible exclusion criteria for edge cases
53-81
: Comprehensive error handling documentation.The webhook events and error handling sections are well-structured with:
- Clear billing rules for different event types
- Comprehensive error scenarios with appropriate responses
- Good user experience considerations
117-119
: HTTP code block properly specified.The HTTP code block correctly specifies the language:
POST /api/webhooks/retell-aiThis addresses the previous review comment about adding language specification to code blocks.
146-168
: Excellent troubleshooting guidance.The troubleshooting section provides practical, actionable steps for common issues:
- Webhook delivery problems
- Credit charging failures
- User call initiation issues
169-182
: Comprehensive security and future planning.The documentation concludes with:
- Important security considerations (authentication, rate limiting, validation)
- Well-thought-out future enhancements
- Practical operational concerns
This demonstrates thorough consideration of both current security needs and future system evolution.
packages/features/ee/workflows/components/WorkflowStepContainer.tsx (5)
94-124
: LGTM! Well-structured helper functions and imports.The new imports and helper functions are well-organized:
CalAIAgentDataSkeleton
provides good loading UXgetActivePhoneNumbers
has proper TypeScript typing and clear filtering logic- Imports align with the Cal.AI functionality being added
138-204
: LGTM! Well-structured state management and mutations.The state variables and TRPC mutations are properly implemented:
- Dialog state management follows React best practices
- Mutations have appropriate success/error handling with user feedback
- Cache invalidation is correctly implemented in success callbacks
- Conditional data fetching prevents unnecessary API calls
241-266
: LGTM! Appropriate conditional logic for Cal.AI actions.The conditional logic correctly skips setting
reminderBody
andemailSubject
for Cal.AI actions since they don't require email templates. This prevents unnecessary form field initialization and aligns with the Cal.AI workflow requirements.
606-614
: LGTM! Proper form state management for Cal.AI actions.The logic correctly handles the transition to Cal.AI actions by:
- Disabling unnecessary UI components (phone number, sender, email fields)
- Clearing irrelevant form fields that don't apply to Cal.AI workflows
- Setting appropriate boolean states for conditional rendering
843-1181
: LGTM! Consistent conditional rendering for Cal.AI actions.The conditional rendering logic properly excludes irrelevant UI sections (sender, email address, message templates) when Cal.AI actions are selected. This creates a clean, focused user experience for Cal.AI workflows while maintaining functionality for traditional email/SMS actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts (1)
40-63
: Add validation for subscription status.The status mapping logic is well-implemented, but consider adding validation for
subscription.status
to handle cases where it might be undefined or null.const statusMap: Record<string, PhoneNumberSubscriptionStatus> = { active: PhoneNumberSubscriptionStatus.ACTIVE, past_due: PhoneNumberSubscriptionStatus.PAST_DUE, cancelled: PhoneNumberSubscriptionStatus.CANCELLED, incomplete: PhoneNumberSubscriptionStatus.INCOMPLETE, incomplete_expired: PhoneNumberSubscriptionStatus.INCOMPLETE_EXPIRED, trialing: PhoneNumberSubscriptionStatus.TRIALING, unpaid: PhoneNumberSubscriptionStatus.UNPAID, }; - const subscriptionStatus = statusMap[subscription.status] || PhoneNumberSubscriptionStatus.UNPAID; + const subscriptionStatus = subscription.status + ? statusMap[subscription.status] || PhoneNumberSubscriptionStatus.UNPAID + : PhoneNumberSubscriptionStatus.UNPAID;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/api/phone-numbers/subscription/success/route.ts
(1 hunks)packages/features/ee/billing/api/webhook/_checkout.session.completed.ts
(2 hunks)packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/api/phone-numbers/subscription/success/route.ts
- packages/features/ee/billing/api/webhook/_checkout.session.completed.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)
**/*.ts
: For Prisma queries, only select data you need; never useinclude
, always useselect
Ensure thecredential.key
field is never returned from tRPC endpoints or APIs
Files:
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()
in hot paths like loops
Files:
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: Udit-takkar
PR: calcom/cal.com#21827
File: packages/lib/server/repository/phoneNumber.ts:153-160
Timestamp: 2025-08-05T13:17:23.491Z
Learning: In the Cal.com Cal AI phone feature, the deletePhoneNumber repository method validation is properly handled in the service layer (RetellAIService.deletePhoneNumber) which validates user ownership and authorization before calling the repository method. The repository layer correctly focuses on data access only.
📚 Learning: the office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is spec...
Learnt from: vijayraghav-io
PR: calcom/cal.com#21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.
Applied to files:
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts
📚 Learning: applies to **/*.ts : for prisma queries, only select data you need; never use `include`, always use ...
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : For Prisma queries, only select data you need; never use `include`, always use `select`
Applied to files:
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts
📚 Learning: applies to **/*.ts : ensure the `credential.key` field is never returned from trpc endpoints or apis...
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : Ensure the `credential.key` field is never returned from tRPC endpoints or APIs
Applied to files:
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts
🧬 Code Graph Analysis (1)
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts (1)
packages/features/ee/billing/api/webhook/__handler.ts (2)
SWHMap
(10-14)HttpCode
(25-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (3)
packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts (3)
1-8
: LGTM! Proper imports and type definitions.The imports are well-organized and the type definition using
SWHMap
provides good type safety for Stripe webhook data.
9-32
: LGTM! Proper error handling and optimized database queries.The handler correctly validates the subscription ID and uses the
select
clause to fetch only required fields, addressing previous optimization concerns. The HTTP 202 status for "Phone number not found" is appropriate for webhook handlers.
34-39
: LGTM! Proper TypeScript types replace previousany
usage.The function signature now uses proper TypeScript types instead of
any
, significantly improving type safety and maintainability. This addresses the previous concern about type definitions.
|
name="phoneNumber" | ||
control={phoneNumberForm.control} | ||
render={({ field: { value } }) => ( | ||
<TextField |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I'll update the placeholder. Edit: It's already updated. I think you are on previous commit |
|
Can't we add this always to the template by default, hidden from the user? I feel like we alway want to end the call at the end of the template, no? |
yes end_call should be there for every prompt but it should be customizable when user wants the agent to end the call. For:- |
But can we make sure in case the user doesn't add end_call, it will still end the call by default when the end of prompt is reached? Or is this already done? |
/> | ||
)} | ||
</div> | ||
<TextArea |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay.
@CarinaWolli do you mean this? |
What does this PR do?
This PR introduces a comprehensive AI phone system that enables users to create AI agents, manage phone numbers, and integrate AI-powered calls into Cal.com workflows
I have used Repository Pattern for database queries (one place instead of scattered), Strategy Pattern so all AI providers work identically (easy to swap), Factory Pattern to create the right provider automatically, Service Pattern to bundle complex operations.
Core AI Phone Capabilities
Workflow Integration
Billing & Credits
More in https://github.com/calcom/cal.com/pull/21827/files#r2251947957
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Environment Variables Required
RETELL_AI_KEY=your_retell_ai_api_key
STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID=price_xxxxxxxxxxxx
Create a workflow and select cal ai voice agent as action and click on setup
https://github.com/user-attachments/assets/bc66ea5f-4b90-493b-93f3-3d8939d16bf5
Click on edit and go to Phone Number tab to buy/import phone number
Screen.Recording.2025-08-05.at.12.59.01.AM.mov
Then you can fill any US based phone number then you see that a task is created in tasker which can be executed if you run tasker
Summary by cubic
Added a self-serve feature for creating and managing AI-powered phone agents for event types, including phone number purchase, agent setup, and test calls.