(null);
// const [toolDialogOpen, setToolDialogOpen] = useState(false);
@@ -593,13 +597,39 @@ export function AgentConfigurationSheet({
-
+
+
+
+
+
+
+ {
+ setIsTestAgentDialogOpen(true);
+ }}>
+ {t("phone_call")}
+
+
+
+ {
+ setIsWebCallDialogOpen(true);
+ }}>
+ {t("web_call")}
+
+
+
+
@@ -964,7 +994,7 @@ export function AgentConfigurationSheet({
{agentId && (
-
)}
+ {agentId && (
+
+ )}
+
setCancellingNumberId(null)}>
void;
agentId: string;
@@ -21,7 +21,7 @@ interface TestAgentDialogProps {
form: UseFormReturn;
}
-export function TestAgentDialog({ open, onOpenChange, agentId, teamId, form }: TestAgentDialogProps) {
+export function TestPhoneCallDialog({ open, onOpenChange, agentId, teamId, form }: TestPhoneCallDialogProps) {
const { t } = useLocale();
const [testPhoneNumber, setTestPhoneNumber] = useState("");
@@ -58,8 +58,7 @@ export function TestAgentDialog({ open, onOpenChange, agentId, teamId, form }: T
};
return (
- // Without modal={false}, the screen freezes when Sheet is open
-
{arePhoneNumbersActive.length > 0 ? (
-
+
+
+
+
+
+
+ setIsTestAgentDialogOpen(true)}>
+ {t("phone_call")}
+
+
+
+ setIsWebCallDialogOpen(true)}>
+ {t("web_call")}
+
+
+
+
) : (
-
+ <>
+
+
+ >
)}
@@ -1447,6 +1480,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}}
readOnly={props.readOnly}
teamId={teamId}
+ isOrganization={props.isOrganization}
workflowId={params?.workflow as string}
workflowStepId={step?.id}
form={form}
@@ -1454,7 +1488,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)}
{stepAgentId && (
-
)}
+ {stepAgentId && (
+
+ )}
+
{/* Unsubscribe Confirmation Dialog */}
diff --git a/packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts b/packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts
index 86e39156bc..e159726872 100644
--- a/packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts
+++ b/packages/trpc/server/routers/viewer/aiVoiceAgent/_router.ts
@@ -1,6 +1,8 @@
import authedProcedure from "../../../procedures/authedProcedure";
import { router } from "../../../trpc";
+import { eventOwnerProcedure } from "../eventTypes/util";
import { ZCreateInputSchema } from "./create.schema";
+import { ZCreateWebCallInputSchema } from "./createWebCall.schema";
import { ZDeleteInputSchema } from "./delete.schema";
import { ZGetInputSchema } from "./get.schema";
import { ZListInputSchema } from "./list.schema";
@@ -61,4 +63,13 @@ export const aiVoiceAgentRouter = router({
input,
});
}),
+
+ createWebCall: eventOwnerProcedure.input(ZCreateWebCallInputSchema).mutation(async ({ ctx, input }) => {
+ const { createWebCallHandler } = await import("./createWebCall.handler");
+
+ return createWebCallHandler({
+ ctx,
+ input,
+ });
+ }),
});
diff --git a/packages/trpc/server/routers/viewer/aiVoiceAgent/createWebCall.handler.ts b/packages/trpc/server/routers/viewer/aiVoiceAgent/createWebCall.handler.ts
new file mode 100644
index 0000000000..0e7fb6521f
--- /dev/null
+++ b/packages/trpc/server/routers/viewer/aiVoiceAgent/createWebCall.handler.ts
@@ -0,0 +1,58 @@
+import { createDefaultAIPhoneServiceProvider } from "@calcom/features/calAIPhone";
+import logger from "@calcom/lib/logger";
+
+import { TRPCError } from "@trpc/server";
+
+import type { TrpcSessionUser } from "../../../types";
+import type { TCreateWebCallInputSchema } from "./createWebCall.schema";
+
+type CreateWebCallHandlerOptions = {
+ ctx: {
+ user: NonNullable;
+ };
+ input: TCreateWebCallInputSchema;
+};
+
+export const createWebCallHandler = async ({ ctx, input }: CreateWebCallHandlerOptions) => {
+ const timeZone = ctx.user.timeZone ?? "Europe/London";
+ const eventTypeId = input.eventTypeId;
+
+ const aiService = createDefaultAIPhoneServiceProvider();
+
+ const agent = await aiService.getAgentWithDetails({
+ id: input.agentId,
+ userId: ctx.user.id,
+ teamId: input.teamId,
+ });
+
+ if (!agent?.providerAgentId) {
+ logger.error(`Agent not found or access denied for agentId ${input.agentId}, userId ${ctx.user.id}`);
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Agent not found or you don't have permission to access this agent",
+ });
+ }
+
+ try {
+ await aiService.updateToolsFromAgentId(agent.providerAgentId, {
+ eventTypeId,
+ timeZone,
+ userId: ctx.user.id,
+ teamId: input.teamId,
+ });
+ } catch (error) {
+ logger.error(`Failed to update tools for agent ${agent.providerAgentId}: ${error}`);
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to configure agent for web call",
+ });
+ }
+
+ return await aiService.createWebCall({
+ agentId: input.agentId,
+ userId: ctx.user.id,
+ teamId: input.teamId,
+ timeZone,
+ eventTypeId,
+ });
+};
diff --git a/packages/trpc/server/routers/viewer/aiVoiceAgent/createWebCall.schema.ts b/packages/trpc/server/routers/viewer/aiVoiceAgent/createWebCall.schema.ts
new file mode 100644
index 0000000000..a7af5a2af7
--- /dev/null
+++ b/packages/trpc/server/routers/viewer/aiVoiceAgent/createWebCall.schema.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+
+export const ZCreateWebCallInputSchema = z.object({
+ agentId: z.string().min(1, "Agent ID is required"),
+ teamId: z.number().optional(),
+ eventTypeId: z.number().min(1, "Event Type ID is required"),
+});
+
+export type TCreateWebCallInputSchema = z.infer;
diff --git a/packages/trpc/server/routers/viewer/credits/_router.tsx b/packages/trpc/server/routers/viewer/credits/_router.tsx
index d871672039..267897a0f5 100644
--- a/packages/trpc/server/routers/viewer/credits/_router.tsx
+++ b/packages/trpc/server/routers/viewer/credits/_router.tsx
@@ -4,11 +4,13 @@ import { router } from "../../../trpc";
import { ZBuyCreditsSchema } from "./buyCredits.schema";
import { ZDownloadExpenseLogSchema } from "./downloadExpenseLog.schema";
import { ZGetAllCreditsSchema } from "./getAllCredits.schema";
+import { ZHasAvailableCreditsSchema } from "./hasAvailableCredits.schema";
type CreditsCache = {
getAllCredits?: typeof import("./getAllCredits.handler").getAllCreditsHandler;
buyCredits?: typeof import("./buyCredits.handler").buyCreditsHandler;
downloadExpenseLog?: typeof import("./downloadExpenseLog.handler").downloadExpenseLogHandler;
+ hasAvailableCredits?: typeof import("./hasAvailableCredits.handler").hasAvailableCreditsHandler;
};
const UNSTABLE_HANDLER_CACHE: CreditsCache = {};
@@ -31,6 +33,23 @@ export const creditsRouter = router({
input,
});
}),
+ hasAvailableCredits: authedProcedure.input(ZHasAvailableCreditsSchema).query(async ({ input, ctx }) => {
+ if (!UNSTABLE_HANDLER_CACHE.hasAvailableCredits) {
+ UNSTABLE_HANDLER_CACHE.hasAvailableCredits = await import("./hasAvailableCredits.handler").then(
+ (mod) => mod.hasAvailableCreditsHandler
+ );
+ }
+
+ // Unreachable code but required for type safety
+ if (!UNSTABLE_HANDLER_CACHE.hasAvailableCredits) {
+ throw new Error("Failed to load handler");
+ }
+
+ return UNSTABLE_HANDLER_CACHE.hasAvailableCredits({
+ ctx,
+ input,
+ });
+ }),
buyCredits: authedProcedure.input(ZBuyCreditsSchema).mutation(async ({ input, ctx }) => {
if (!UNSTABLE_HANDLER_CACHE.buyCredits) {
UNSTABLE_HANDLER_CACHE.buyCredits = await import("./buyCredits.handler").then(
diff --git a/packages/trpc/server/routers/viewer/credits/hasAvailableCredits.handler.ts b/packages/trpc/server/routers/viewer/credits/hasAvailableCredits.handler.ts
new file mode 100644
index 0000000000..548a89812b
--- /dev/null
+++ b/packages/trpc/server/routers/viewer/credits/hasAvailableCredits.handler.ts
@@ -0,0 +1,25 @@
+import type { TrpcSessionUser } from "@calcom/trpc/server/types";
+
+import type { THasAvailableCreditsSchema } from "./hasAvailableCredits.schema";
+
+type HasAvailableCreditsOptions = {
+ ctx: {
+ user: NonNullable;
+ };
+ input: THasAvailableCreditsSchema;
+};
+
+export const hasAvailableCreditsHandler = async ({ ctx, input }: HasAvailableCreditsOptions) => {
+ const { teamId } = input;
+
+ const orgId = ctx.user.organization?.id;
+
+ const { CreditService } = await import("@calcom/features/ee/billing/credit-service");
+ const creditService = new CreditService();
+
+ const hasCredits = await creditService.hasAvailableCredits({
+ userId: ctx.user.id,
+ teamId: teamId ?? orgId,
+ });
+ return hasCredits;
+};
diff --git a/packages/trpc/server/routers/viewer/credits/hasAvailableCredits.schema.ts b/packages/trpc/server/routers/viewer/credits/hasAvailableCredits.schema.ts
new file mode 100644
index 0000000000..fbf29dfccd
--- /dev/null
+++ b/packages/trpc/server/routers/viewer/credits/hasAvailableCredits.schema.ts
@@ -0,0 +1,7 @@
+import { z } from "zod";
+
+export const ZHasAvailableCreditsSchema = z.object({
+ teamId: z.number().optional(),
+});
+
+export type THasAvailableCreditsSchema = z.infer;
diff --git a/packages/ui/components/icon/icon-list.mjs b/packages/ui/components/icon/icon-list.mjs
index 51c3428629..694ba7a401 100644
--- a/packages/ui/components/icon/icon-list.mjs
+++ b/packages/ui/components/icon/icon-list.mjs
@@ -148,4 +148,8 @@ export const lucideIconList = new Set([
"bookmark",
"square-pen",
"list-filter",
+ "phone-off",
+ "mic-off",
+ "mic",
+ "monitor",
]);
diff --git a/packages/ui/components/icon/icon-names.ts b/packages/ui/components/icon/icon-names.ts
index ff6139851c..accd3795ef 100644
--- a/packages/ui/components/icon/icon-names.ts
+++ b/packages/ui/components/icon/icon-names.ts
@@ -98,11 +98,15 @@ export type IconName =
| "menu"
| "message-circle"
| "messages-square"
+ | "mic-off"
+ | "mic"
+ | "monitor"
| "moon"
| "paintbrush"
| "paperclip"
| "pencil"
| "phone-call"
+ | "phone-off"
| "phone"
| "plus"
| "refresh-ccw"
diff --git a/yarn.lock b/yarn.lock
index cee6bd69bb..6c05002c5b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2640,6 +2640,13 @@ __metadata:
languageName: node
linkType: hard
+"@bufbuild/protobuf@npm:^1.10.0":
+ version: 1.10.1
+ resolution: "@bufbuild/protobuf@npm:1.10.1"
+ checksum: 403838ad278d504e33e72ec0f64ce1bac9f5025ee6396253382e821a1fe0c371a58ffe45a0e8f23306205b6890c5f83e85828168a35dae118915cd4c3d091177
+ languageName: node
+ linkType: hard
+
"@bundled-es-modules/cookie@npm:^2.0.1":
version: 2.0.1
resolution: "@bundled-es-modules/cookie@npm:2.0.1"
@@ -4358,6 +4365,7 @@ __metadata:
react-use-intercom: 1.5.1
recoil: ^0.7.7
remove-markdown: ^0.5.0
+ retell-client-js-sdk: ^2.0.0
retell-sdk: ^4.40.0
rrule: ^2.7.1
sanitize-html: ^2.10.0
@@ -8988,6 +8996,22 @@ __metadata:
languageName: node
linkType: hard
+"@livekit/mutex@npm:1.1.1":
+ version: 1.1.1
+ resolution: "@livekit/mutex@npm:1.1.1"
+ checksum: 44a31eb7a913357ffb57d04eaa20f7507c0a659638c6dfaba9a413c21a3397aa351497f7c77bca3c06a29ac2cbe83698a5f96b9230012a24b86ac8366e9b8666
+ languageName: node
+ linkType: hard
+
+"@livekit/protocol@npm:1.39.3":
+ version: 1.39.3
+ resolution: "@livekit/protocol@npm:1.39.3"
+ dependencies:
+ "@bufbuild/protobuf": ^1.10.0
+ checksum: 72aa3b9e82203ccec039047518d841d65b10dcc11ab9d661f269eab8a8672aed7b2d072f4f09cc6fb94a2579542b0a9216d7f66476d34cd1675c13e1ebcc119f
+ languageName: node
+ linkType: hard
+
"@ljharb/through@npm:^2.3.11":
version: 2.3.13
resolution: "@ljharb/through@npm:2.3.13"
@@ -33757,6 +33781,25 @@ __metadata:
languageName: node
linkType: hard
+"livekit-client@npm:^2.5.1":
+ version: 2.15.6
+ resolution: "livekit-client@npm:2.15.6"
+ dependencies:
+ "@livekit/mutex": 1.1.1
+ "@livekit/protocol": 1.39.3
+ events: ^3.3.0
+ loglevel: ^1.9.2
+ sdp-transform: ^2.15.0
+ ts-debounce: ^4.0.0
+ tslib: 2.8.1
+ typed-emitter: ^2.1.0
+ webrtc-adapter: ^9.0.1
+ peerDependencies:
+ "@types/dom-mediacapture-record": ^1
+ checksum: 3dda194dbb5c9ff83038fafa8ead353e9abea84bdd7fdb4c65e164bcfab2d48ccec04caf6df2bace164c4475c4a6ae753c6154654abca984fe96384d885721af
+ languageName: node
+ linkType: hard
+
"load-bmfont@npm:^1.3.1, load-bmfont@npm:^1.4.0":
version: 1.4.1
resolution: "load-bmfont@npm:1.4.1"
@@ -34102,6 +34145,13 @@ __metadata:
languageName: node
linkType: hard
+"loglevel@npm:^1.9.2":
+ version: 1.9.2
+ resolution: "loglevel@npm:1.9.2"
+ checksum: 896c67b90a507bfcfc1e9a4daa7bf789a441dd70d95cd13b998d6dd46233a3bfadfb8fadb07250432bbfb53bf61e95f2520f9b11f9d3175cc460e5c251eca0af
+ languageName: node
+ linkType: hard
+
"long-timeout@npm:0.1.1":
version: 0.1.1
resolution: "long-timeout@npm:0.1.1"
@@ -42186,6 +42236,16 @@ __metadata:
languageName: node
linkType: hard
+"retell-client-js-sdk@npm:^2.0.0":
+ version: 2.0.7
+ resolution: "retell-client-js-sdk@npm:2.0.7"
+ dependencies:
+ eventemitter3: ^5.0.1
+ livekit-client: ^2.5.1
+ checksum: 8c0f0a0ed95b79a7c0e4b9dad1f1b0369c8e05bc6e0219945ad271426e5348d644d113b94d2c9a35eff62919748a3f638c35e76e3ddb413fa5062295b4de17e4
+ languageName: node
+ linkType: hard
+
"retell-sdk@npm:^4.40.0":
version: 4.41.0
resolution: "retell-sdk@npm:4.41.0"
@@ -42667,6 +42727,15 @@ __metadata:
languageName: node
linkType: hard
+"rxjs@npm:*, rxjs@npm:^7.8.2":
+ version: 7.8.2
+ resolution: "rxjs@npm:7.8.2"
+ dependencies:
+ tslib: ^2.1.0
+ checksum: 2f233d7c832a6c255dabe0759014d7d9b1c9f1cb2f2f0d59690fd11c883c9826ea35a51740c06ab45b6ade0d9087bde9192f165cba20b6730d344b831ef80744
+ languageName: node
+ linkType: hard
+
"rxjs@npm:7.8.1, rxjs@npm:^7.8.1":
version: 7.8.1
resolution: "rxjs@npm:7.8.1"
@@ -42685,15 +42754,6 @@ __metadata:
languageName: node
linkType: hard
-"rxjs@npm:^7.8.2":
- version: 7.8.2
- resolution: "rxjs@npm:7.8.2"
- dependencies:
- tslib: ^2.1.0
- checksum: 2f233d7c832a6c255dabe0759014d7d9b1c9f1cb2f2f0d59690fd11c883c9826ea35a51740c06ab45b6ade0d9087bde9192f165cba20b6730d344b831ef80744
- languageName: node
- linkType: hard
-
"safe-array-concat@npm:^1.0.1":
version: 1.1.0
resolution: "safe-array-concat@npm:1.1.0"
@@ -42939,6 +42999,22 @@ __metadata:
languageName: node
linkType: hard
+"sdp-transform@npm:^2.15.0":
+ version: 2.15.0
+ resolution: "sdp-transform@npm:2.15.0"
+ bin:
+ sdp-verify: checker.js
+ checksum: 63a21fdb3f97adc31019c7fb896e77076bdd2405d9e0eb6bd991b6f51973552bdd99409878aae8f8375f516af162d5a4176e102e090e24c870accafb9f73886f
+ languageName: node
+ linkType: hard
+
+"sdp@npm:^3.2.0":
+ version: 3.2.1
+ resolution: "sdp@npm:3.2.1"
+ checksum: 88c24ceb46bf29f7ad3ece249db6353668a7c970b5831cbf1f4f371d8960ae90a4b0e3b9b05aa499a5038fc145662578456530c7b3efcaf623fdb0ef8ab243a0
+ languageName: node
+ linkType: hard
+
"section-matter@npm:^1.0.0":
version: 1.0.0
resolution: "section-matter@npm:1.0.0"
@@ -45943,6 +46019,13 @@ __metadata:
languageName: node
linkType: hard
+"ts-debounce@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "ts-debounce@npm:4.0.0"
+ checksum: e1e509632c5aa09c40d3fa315b3a95b2c2e8813ccc706a400aa08e41f691e658061f34b42a1e8a578a043540d6db198e6ecf3ce26a5356a02a0940985fb1e379
+ languageName: node
+ linkType: hard
+
"ts-dedent@npm:^2.0.0":
version: 2.2.0
resolution: "ts-dedent@npm:2.2.0"
@@ -46250,7 +46333,7 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:2, tslib@npm:^2.0.3, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.0, tslib@npm:^2.8.1":
+"tslib@npm:2, tslib@npm:2.8.1, tslib@npm:^2.0.3, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.0, tslib@npm:^2.8.1":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a
@@ -46740,6 +46823,18 @@ __metadata:
languageName: node
linkType: hard
+"typed-emitter@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "typed-emitter@npm:2.1.0"
+ dependencies:
+ rxjs: "*"
+ dependenciesMeta:
+ rxjs:
+ optional: true
+ checksum: 95821a9e05784b972cc9d152891fd12a56cb4b1a7c57e768c02bea6a8984da7aff8f19404a7b69eea11fae2a3b6c0c510a4c510f575f50162c759ae9059f2520
+ languageName: node
+ linkType: hard
+
"typedarray-to-buffer@npm:~1.0.0":
version: 1.0.4
resolution: "typedarray-to-buffer@npm:1.0.4"
@@ -48305,6 +48400,15 @@ __metadata:
languageName: node
linkType: hard
+"webrtc-adapter@npm:^9.0.1":
+ version: 9.0.3
+ resolution: "webrtc-adapter@npm:9.0.3"
+ dependencies:
+ sdp: ^3.2.0
+ checksum: 2b29e6e6ed1018f62b2077fffd4f6d7fa9993f98091b7af459261092c2c05125bfa146b4d0dd76ed9e667703a1391743e0bb76a3754239c7d20acb034026eae3
+ languageName: node
+ linkType: hard
+
"websocket-driver@npm:>=0.5.1":
version: 0.7.4
resolution: "websocket-driver@npm:0.7.4"