Skip to content

Conversation

@Udit-takkar
Copy link
Contributor

@Udit-takkar Udit-takkar commented Sep 4, 2025

What does this PR do?

  • Fixes #XXXX (GitHub issue number)
  • Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

Added environment variable-based configuration to enable local testing of Retell AI services using production event type IDs and API keys.

  • RETELL_AI_TEST_MODE: Boolean flag to enable/disable test mode
    • RETELL_AI_TEST_EVENT_TYPE_MAP: JSON mapping of local to production event type IDs
    • RETELL_AI_TEST_CAL_API_KEY: Production API key for testing

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • N/A I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • N/A I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 4, 2025

Walkthrough

Adds test-mode overrides for Retell AI. .env.example introduces RETELL_AI_TEST_MODE, RETELL_AI_TEST_EVENT_TYPE_MAP, and RETELL_AI_TEST_CAL_API_KEY. turbo.json registers these in globalEnv. packages/lib/constants.ts exports RETELL_AI_TEST_MODE (boolean) and RETELL_AI_TEST_EVENT_TYPE_MAP (JSON-parsed map with warning on parse failure). AgentService.ts consumes these: when test mode is enabled, maps event type IDs for tool existence checks, creation, assignments, and cleanup/removal; logs mapped IDs; and optionally uses RETELL_AI_TEST_CAL_API_KEY for API access. Non-test-mode behavior remains unchanged. No exported/public API signatures are altered.

Possibly related PRs

  • feat: Cal.ai Self Serve #2  #22995 — Modifies Retell AI AgentService tooling and event-type ID handling in the same code paths that this PR extends with test-mode and mapping logic.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/local-test-retel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@graphite-app graphite-app bot requested a review from a team September 4, 2025 09:20
@github-actions github-actions bot added the ❗️ .env changes contains changes to env variables label Sep 4, 2025
@keithwillcode keithwillcode added core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO labels Sep 4, 2025
@dosubot dosubot bot added the ai area: AI, cal.ai label Sep 4, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (1)

60-76: Add agent & event type ownership checks in updateToolsFromAgentId

  • packages/trpc/server/routers/viewer/workflows/update.handler.ts already enforces isAuthorized(userWorkflow, ctx.user.id, "workflow.update") before calling the service.
  • packages/trpc/server/routers/viewer/aiVoiceAgent/testCall.handler.ts invokes updateToolsFromAgentId with no ownership guard.
  • Move ownership assertions into AgentService.updateToolsFromAgentId so all call sites are protected:
   async updateToolsFromAgentId(
     agentId: string,
     data: { eventTypeId: number | null; timeZone: string; userId: number | null; teamId?: number | null }
   ) {
     if (!agentId?.trim()) {
       throw new HttpError({ statusCode: 400, message: "Agent ID is required and cannot be empty" });
     }

     if (!data.eventTypeId || !data.userId) {
       throw new HttpError({ statusCode: 400, message: "Event type ID and user ID are required" });
     }

+    // Verify agent ownership/admin access
+    const hasAgentAccess = await this.agentRepository.findByIdWithAdminAccess({
+      id: /* resolve internal agent DB id for providerAgentId */,
+      userId: data.userId,
+      teamId: data.teamId ?? undefined,
+    });
+    if (!hasAgentAccess) {
+      throw new HttpError({ statusCode: 403, message: "No permission to modify this agent." });
+    }
+
+    // Verify event type ownership
+    await this.agentRepository.assertCanManageEventType({
+      userId: data.userId,
+      teamId: data.teamId ?? undefined,
+      eventTypeId: data.eventTypeId,
+    });
🧹 Nitpick comments (6)
.env.example (2)

391-393: Fix dotenv-linter ordering and clarify JSON guidance

Reorder and tighten the comment so users don’t paste invalid JSON. This also resolves the UnorderedKey warnings.

Apply:

-# 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=
+# JSON mapping from local → production event type IDs.
+# Keys must be strings (JSON requirement); values must be numbers. Example: {"50": 747280}
+RETELL_AI_TEST_CAL_API_KEY=
+RETELL_AI_TEST_EVENT_TYPE_MAP=

390-394: Optional: keep keys sorted for consistency

If you prefer alpha order for this block: CAL_API_KEY, EVENT_TYPE_MAP, MODE.

-RETELL_AI_TEST_MODE=false
-RETELL_AI_TEST_CAL_API_KEY=
-RETELL_AI_TEST_EVENT_TYPE_MAP=
+RETELL_AI_TEST_CAL_API_KEY=
+RETELL_AI_TEST_EVENT_TYPE_MAP=
+RETELL_AI_TEST_MODE=false
packages/lib/constants.ts (1)

245-254: Type and validate RETELL_AI_TEST_EVENT_TYPE_MAP to avoid “any” and bad inputs

Strong-typing prevents accidental non-numeric values; also trims junk.

-// 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;
-  }
-})();
+// 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: Record<string, number> | null = (() => {
+  const raw = process.env.RETELL_AI_TEST_EVENT_TYPE_MAP;
+  if (!raw) return null;
+  try {
+    const parsed = JSON.parse(raw) as Record<string, unknown>;
+    const out: Record<string, number> = {};
+    for (const [k, v] of Object.entries(parsed)) {
+      const n = typeof v === "string" ? Number(v.trim()) : (v as number);
+      if (Number.isFinite(n)) out[String(k)] = Math.trunc(n);
+    }
+    return Object.keys(out).length ? Object.freeze(out) : null;
+  } catch (e) {
+    console.warn("Failed to parse RETELL_AI_TEST_EVENT_TYPE_MAP", e);
+    return null;
+  }
+})();
packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (3)

85-91: DRY the eventTypeId mapping into a helper to reduce mistakes

Mapping logic is duplicated in three places; centralize to avoid divergence.

 export class AgentService {
   private logger = logger.getSubLogger({ prefix: ["AgentService"] });
 
+  // Map local → production eventTypeId in test mode
+  private mapEventTypeId = (id: number): number => {
+    if (RETELL_AI_TEST_MODE && RETELL_AI_TEST_EVENT_TYPE_MAP) {
+      const mapped = RETELL_AI_TEST_EVENT_TYPE_MAP[String(id)];
+      return mapped ? Number(mapped) : id;
+    }
+    return id;
+  };
+
   constructor(
-    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;
-    }
+    const eventTypeId = this.mapEventTypeId(data.eventTypeId);
-    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;
-      });
-    }
+    const mappedEventTypeIds = eventTypeIds.map((id) => this.mapEventTypeId(id));
-    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;
-      });
-    }
+    const mappedActiveEventTypeIds = activeEventTypeIds.map((id) => this.mapEventTypeId(id));

Also applies to: 193-201, 263-271


124-131: API key selection path: safe precedence; add tiny guard to avoid empty strings

If RETELL_AI_TEST_CAL_API_KEY is set to an empty string, it will be truthy in some shells but falsy here; explicitly trim.

-      RETELL_AI_TEST_MODE && process.env.RETELL_AI_TEST_CAL_API_KEY
-          ? process.env.RETELL_AI_TEST_CAL_API_KEY
+      RETELL_AI_TEST_MODE && (process.env.RETELL_AI_TEST_CAL_API_KEY?.trim() ?? "") !== ""
+          ? process.env.RETELL_AI_TEST_CAL_API_KEY!.trim()
           : reusableKey ??

221-227: Minor perf/readability: build a Set for membership checks

O(n*m) includes() is fine for small arrays, but Set clarifies intent and is linear.

-      const toolNamesToRemove = mappedEventTypeIds.flatMap((eventTypeId) => [
-        `check_availability_${eventTypeId}`,
-        `book_appointment_${eventTypeId}`,
-      ]);
-
-      const filteredTools = existing.filter((tool) => !toolNamesToRemove.includes(tool.name));
+      const toolNamesToRemove = new Set(
+        mappedEventTypeIds.flatMap((id) => [`check_availability_${id}`, `book_appointment_${id}`])
+      );
+      const filteredTools = existing.filter((tool) => !toolNamesToRemove.has(tool.name));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b052c2e and 35d2fdf.

📒 Files selected for processing (4)
  • .env.example (1 hunks)
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (10 hunks)
  • packages/lib/constants.ts (1 hunks)
  • turbo.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/constants.ts
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.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/lib/constants.ts
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/constants.ts
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
**/*Service.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Service files must include Service suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts)

Files:

  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
🧠 Learnings (4)
📚 Learning: 2025-08-21T16:30:19.309Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/aiVoiceAgent/get.handler.ts:16-20
Timestamp: 2025-08-21T16:30:19.309Z
Learning: In calcom/cal.com Cal AI voice agent endpoints, it's acceptable to return cal_api_key values in the general_tools array when they belong to the requesting user themselves. This is an exception to the general rule about not returning credential.key fields, as users need to see their own API keys for configuration purposes (per maintainer Udit-takkar in PR #22995).

Applied to files:

  • .env.example
  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
📚 Learning: 2025-08-27T12:15:43.830Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/trpc/server/routers/viewer/aiVoiceAgent/testCall.handler.ts:41-44
Timestamp: 2025-08-27T12:15:43.830Z
Learning: In calcom/cal.com, the AgentService.getAgent() method in packages/features/calAIPhone/providers/retellAI/services/AgentService.ts does NOT include authorization checks - it only validates the agentId parameter and directly calls the repository without verifying user/team access. This contrasts with other methods like getAgentWithDetails() which properly use findByIdWithUserAccessAndDetails() for authorization. When reviewing updateToolsFromAgentId() calls, always verify both agent ownership and eventType ownership are checked.

Applied to files:

  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
📚 Learning: 2025-08-08T09:27:23.896Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/providers/retellAI/services/AgentService.ts:195-216
Timestamp: 2025-08-08T09:27:23.896Z
Learning: In PR calcom/cal.com#22919, file packages/features/calAIPhone/providers/retellAI/services/AgentService.ts, the updateAgentConfiguration method intentionally does not persist the optional `name` parameter to the repository for now, per maintainer (Udit-takkar). Future reviews should not flag this unless requirements change.

Applied to files:

  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
📚 Learning: 2025-08-08T09:29:11.681Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts:118-143
Timestamp: 2025-08-08T09:29:11.681Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (TypeScript), the AIPhoneServiceAgentListItem is required to include user.email in listAgents responses (per maintainer Udit-takkar). Future reviews should not flag this as unnecessary PII unless requirements change.

Applied to files:

  • packages/features/calAIPhone/providers/retellAI/services/AgentService.ts
🧬 Code graph analysis (1)
packages/features/calAIPhone/providers/retellAI/services/AgentService.ts (2)
packages/lib/constants.ts (2)
  • RETELL_AI_TEST_MODE (245-245)
  • RETELL_AI_TEST_EVENT_TYPE_MAP (246-254)
packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (1)
  • AIPhoneServiceTools (81-82)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 392-392: [UnorderedKey] The RETELL_AI_TEST_EVENT_TYPE_MAP key should go before the RETELL_AI_TEST_MODE key

(UnorderedKey)


[warning] 393-393: [UnorderedKey] The RETELL_AI_TEST_CAL_API_KEY key should go before the RETELL_AI_TEST_EVENT_TYPE_MAP key

(UnorderedKey)

🔇 Additional comments (1)
turbo.json (1)

152-154: Confirm server-only usage of RETELL test env vars
• Found references in packages/lib/constants.ts (lines 245, 247, 249) and services/AgentService.ts (lines 124–125); ensure neither file is imported into any client-side bundle to avoid exposing secrets.

@vercel
Copy link

vercel bot commented Sep 5, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Sep 5, 2025 7:27pm
cal-eu Ignored Ignored Sep 5, 2025 7:27pm

@github-actions
Copy link
Contributor

github-actions bot commented Sep 5, 2025

E2E results are ready!

@Udit-takkar Udit-takkar merged commit c42e803 into main Sep 5, 2025
60 of 62 checks passed
@Udit-takkar Udit-takkar deleted the chore/local-test-retel branch September 5, 2025 20:41
@coderabbitai coderabbitai bot mentioned this pull request Sep 19, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai area: AI, cal.ai core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO ❗️ .env changes contains changes to env variables ready-for-e2e size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants