diff --git a/apps/ai/.env.example b/apps/ai/.env.example
deleted file mode 100644
index e6effa0a1e..0000000000
--- a/apps/ai/.env.example
+++ /dev/null
@@ -1,21 +0,0 @@
-BACKEND_URL=http://localhost:3002/api
-# BACKEND_URL=https://api.cal.com/v1
-FRONTEND_URL=http://localhost:3000
-# FRONTEND_URL=https://cal.com
-
-APP_ID=cal-ai
-APP_URL=http://localhost:3000/apps/cal-ai
-
-# This is for the onboard route. Which domain should we send emails from?
-SENDER_DOMAIN=cal.ai
-
-# Used to verify requests from sendgrid. You can generate a new one with: `openssl rand -hex 32`
-PARSE_KEY=
-
-OPENAI_API_KEY=
-
-# Optionally trace completions at https://smith.langchain.com
-# LANGCHAIN_TRACING_V2=true
-# LANGCHAIN_ENDPOINT=
-# LANGCHAIN_API_KEY=
-# LANGCHAIN_PROJECT=
diff --git a/apps/ai/README.md b/apps/ai/README.md
deleted file mode 100644
index 306bf07906..0000000000
--- a/apps/ai/README.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# Cal.ai
-
-Welcome to [Cal.ai](https://cal.ai)!
-
-This app lets you chat with your calendar via email:
-
-- Turn informal emails into bookings eg. forward "wanna meet tmrw at 2pm?"
-- List and rearrange your bookings eg. "clear my afternoon"
-- Answer basic questions about your busiest times eg. "how does my Tuesday look?"
-
-The core logic is contained in [agent/route.ts](/apps/ai/src/app/api/agent/route.ts). Here, a [LangChain Agent Executor](https://docs.langchain.com/docs/components/agents/agent-executor) is tasked with following your instructions. Given your last-known timezone, working hours, and busy times, it attempts to CRUD your bookings.
-
-_The AI agent can only choose from a set of tools, without ever seeing your API key._
-
-Emails are cleaned and routed in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts) using [MailParser](https://nodemailer.com/extras/mailparser/).
-
-Incoming emails are routed by email address. Addresses are verified by [DKIM record](https://support.google.com/a/answer/174124?hl=en), making them hard to spoof.
-
-## Recognition
-
-
-
-## Getting Started
-
-### Development
-
-If you haven't yet, please run the [root setup](/README.md) steps.
-
-Before running the app, please see [env.mjs](./src/env.mjs) for all required environment variables. Run `cp .env.example .env` in this folder to get started. You'll need:
-
-- An [OpenAI API key](https://platform.openai.com/account/api-keys) with access to GPT-4
-- A [SendGrid API key](https://app.sendgrid.com/settings/api_keys)
-- A default sender email (for example, `me@dev.example.com`)
-- The Cal.ai app's ID and URL (see [add.ts](/packages/app-store/cal-ai/api/index.ts))
-- A unique value for `PARSE_KEY` with `openssl rand -hex 32`
-
-To stand up the API and AI apps simultaneously, simply run `yarn dev:ai`.
-
-### Agent Architecture
-
-The scheduling agent in [agent/route.ts](/apps/ai/src/app/api/agent/route.ts) calls an LLM (in this case, GPT-4) in a loop to accomplish a multi-step task. We use an [OpenAI Functions agent](https://js.langchain.com/docs/modules/agents/agent_types/openai_functions_agent), which is fine-tuned to output text suited for passing to tools.
-
-Tools (eg. [`createBooking`](/apps/ai/src/tools/createBooking.ts)) are simply JavaScript methods wrapped by Zod schemas, telling the agent what format to output.
-
-Here is the full architecture:
-
-
-
-### Email Router
-
-To expose the AI app, you can use either [Tunnelmole](https://github.com/robbie-cahill/tunnelmole-client), an open source tunnelling tool; or [nGrok](https://ngrok.com/), a popular closed source tunnelling tool.
-
-For Tunnelmole, run `tmole 3005` (or the AI app's port number) in a new terminal. Please replace `3005` with the port number if it is different. In the output, you'll see two URLs, one http and a https (we recommend using the https url for privacy and security). To install Tunnelmole, use `curl -O https://install.tunnelmole.com/8dPBw/install && sudo bash install`. (On Windows, download [tmole.exe](https://tunnelmole.com/downloads/tmole.exe))
-
-For nGrok, run `ngrok http 3005` (or the AI app's port number) in a new terminal. You may need to install nGrok first.
-
-To forward incoming emails to the serverless function at `/agent`, we use [SendGrid's Inbound Parse](https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook).
-
-1. Ensure you have a [SendGrid account](https://signup.sendgrid.com/)
-2. Ensure you have an authenticated domain. Go to Settings > Sender Authentication > Authenticate. For DNS host, select `I'm not sure`. Click Next and add your domain, eg. `example.com`. Choose Manual Setup. You'll be given three CNAME records to add to your DNS settings, eg. in [Vercel Domains](https://vercel.com/dashboard/domains). After adding those records, click Verify. To troubleshoot, see the [full instructions](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication).
-3. Authorize your domain for email with MX records: one with name `[your domain].com` and value `mx.sendgrid.net.`, and another with name `bounces.[your domain].com` and value `feedback-smtp.us-east-1.amazonses.com`. Set the priority to `10` if prompted.
-4. Go to Settings > [Inbound Parse](https://app.sendgrid.com/settings/parse) > Add Host & URL. Choose your authenticated domain.
-5. In the Destination URL field, use the Tunnelmole or ngrok URL from above along with the path, `/api/receive`, and one param, `parseKey`, which lives in [this app's .env](/apps/ai/.env.example) under `PARSE_KEY`. The full URL should look like `https://abc.tunnelmole.net/api/receive?parseKey=ABC-123` or `https://abc.ngrok.io/api/receive?parseKey=ABC-123`.
-6. Activate "POST the raw, full MIME message".
-7. Send an email to `[anyUsername]@example.com`. You should see a ping on the Tunnelmole or ngrok listener and server.
-8. Adjust the logic in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts), save to hot-reload, and send another email to test the behaviour.
-
-Please feel free to improve any part of this architecture!
diff --git a/apps/ai/next-env.d.ts b/apps/ai/next-env.d.ts
deleted file mode 100644
index 4f11a03dc6..0000000000
--- a/apps/ai/next-env.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-///
-///
-
-// NOTE: This file should not be edited
-// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/apps/ai/next.config.js b/apps/ai/next.config.js
deleted file mode 100644
index 747ded2324..0000000000
--- a/apps/ai/next.config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-const withBundleAnalyzer = require("@next/bundle-analyzer");
-
-const plugins = [];
-plugins.push(withBundleAnalyzer({ enabled: process.env.ANALYZE === "true" }));
-
-/** @type {import("next").NextConfig} */
-const nextConfig = {
- async redirects() {
- return [
- {
- source: "/",
- destination: "https://cal.com/ai",
- permanent: true,
- },
- ];
- },
- i18n: {
- defaultLocale: "en",
- locales: ["en"],
- },
- reactStrictMode: true,
-};
-
-module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
diff --git a/apps/ai/package.json b/apps/ai/package.json
deleted file mode 100644
index 3bd4127264..0000000000
--- a/apps/ai/package.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "@calcom/ai",
- "version": "1.2.1",
- "private": true,
- "author": "Cal.com Inc.",
- "dependencies": {
- "@calcom/prisma": "*",
- "@langchain/core": "^0.1.26",
- "@langchain/openai": "^0.0.14",
- "@t3-oss/env-nextjs": "^0.6.1",
- "langchain": "^0.1.17",
- "mailparser": "^3.6.5",
- "next": "^13.5.4",
- "supports-color": "8.1.1",
- "zod": "^3.22.4"
- },
- "devDependencies": {
- "@types/mailparser": "^3.4.0"
- },
- "scripts": {
- "build": "next build",
- "dev": "next dev -p 3005",
- "format": "npx prettier . --write",
- "lint": "eslint . --ext .ts,.js,.tsx,.jsx",
- "lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
- "start": "next start"
- }
-}
diff --git a/apps/ai/src/app/api/agent/route.ts b/apps/ai/src/app/api/agent/route.ts
deleted file mode 100644
index 0ffbfa36b0..0000000000
--- a/apps/ai/src/app/api/agent/route.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { NextRequest } from "next/server";
-import { NextResponse } from "next/server";
-
-import agent from "../../../utils/agent";
-import sendEmail from "../../../utils/sendEmail";
-import { verifyParseKey } from "../../../utils/verifyParseKey";
-
-// Allow agent loop to run for up to 5 minutes
-export const maxDuration = 300;
-
-/**
- * Launches a LangChain agent to process an incoming email,
- * then sends the response to the user.
- */
-export const POST = async (request: NextRequest) => {
- const verified = verifyParseKey(request.url);
-
- if (!verified) {
- return new NextResponse("Unauthorized", { status: 401 });
- }
-
- const json = await request.json();
-
- const { apiKey, userId, message, subject, user, users, replyTo: agentEmail } = json;
-
- if ((!message && !subject) || !user) {
- return new NextResponse("Missing fields", { status: 400 });
- }
-
- try {
- const response = await agent(`${subject}\n\n${message}`, { ...user }, users, apiKey, userId, agentEmail);
-
- // Send response to user
- await sendEmail({
- subject: `Re: ${subject}`,
- text: response.replace(/(?:\r\n|\r|\n)/g, "\n"),
- to: user.email,
- from: agentEmail,
- });
-
- return new NextResponse("ok");
- } catch (error) {
- await sendEmail({
- subject: `Re: ${subject}`,
- text: "Thanks for using Cal.ai! We're experiencing high demand and can't currently process your request. Please try again later.",
- to: user.email,
- from: agentEmail,
- });
-
- return new NextResponse(
- (error as Error).message || "Something went wrong. Please try again or reach out for help.",
- { status: 500 }
- );
- }
-};
diff --git a/apps/ai/src/app/api/onboard/route.ts b/apps/ai/src/app/api/onboard/route.ts
deleted file mode 100644
index e4eaaef8cf..0000000000
--- a/apps/ai/src/app/api/onboard/route.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { NextRequest } from "next/server";
-
-import prisma from "@calcom/prisma";
-
-import { env } from "../../../env.mjs";
-import sendEmail from "../../../utils/sendEmail";
-
-export const POST = async (request: NextRequest) => {
- const { userId } = await request.json();
-
- const user = await prisma.user.findUnique({
- select: {
- email: true,
- name: true,
- username: true,
- },
- where: {
- id: userId,
- },
- });
-
- if (!user) {
- return new Response("User not found", { status: 404 });
- }
-
- await sendEmail({
- subject: "Welcome to Cal AI",
- to: user.email,
- from: `${user.username}@${env.SENDER_DOMAIN}`,
- text: `Hi ${
- user.name || `@${user.username}`
- },\n\nI'm Cal AI, your personal booking assistant! I'll be here, 24/7 to help manage your busy schedule and find times to meet with the people you care about.\n\nHere are some things you can ask me:\n\n- "Book a meeting with @someone" (The @ symbol lets you tag Cal.com users)\n- "What meetings do I have today?" (I'll show you your schedule)\n- "Find a time for coffee with someone@gmail.com" (I'll intro and send them some good times)\n\nI'm still learning, so if you have any feedback, please tweet it to @calcom!\n\nRemember, you can always reach me here, at ${
- user.username
- }@${
- env.SENDER_DOMAIN
- }.\n\nLooking forward to working together (:\n\n- Cal AI, Your personal booking assistant`,
- html: `Hi ${
- user.name || `@${user.username}`
- },
I'm Cal AI, your personal booking assistant! I'll be here, 24/7 to help manage your busy schedule and find times to meet with the people you care about.
Here are some things you can ask me:
- "Book a meeting with @someone" (The @ symbol lets you tag Cal.com users)
- "What meetings do I have today?" (I'll show you your schedule)
- "Find a time for coffee with someone@gmail.com" (I'll intro and send them some good times)
I'm still learning, so if you have any feedback, please send it to @calcom on X!
Remember, you can always reach me here, at ${
- user.username
- }@${env.SENDER_DOMAIN}.
Looking forward to working together (:
- Cal AI`,
- });
- return new Response("OK", { status: 200 });
-};
diff --git a/apps/ai/src/app/api/receive/route.ts b/apps/ai/src/app/api/receive/route.ts
deleted file mode 100644
index 68bbc51168..0000000000
--- a/apps/ai/src/app/api/receive/route.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import type { ParsedMail, Source } from "mailparser";
-import { simpleParser } from "mailparser";
-import type { NextRequest } from "next/server";
-import { NextResponse } from "next/server";
-
-import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
-import prisma from "@calcom/prisma";
-
-import { env } from "../../../env.mjs";
-import { fetchAvailability } from "../../../tools/getAvailability";
-import { fetchEventTypes } from "../../../tools/getEventTypes";
-import { extractUsers } from "../../../utils/extractUsers";
-import getHostFromHeaders from "../../../utils/host";
-import now from "../../../utils/now";
-import sendEmail from "../../../utils/sendEmail";
-import { verifyParseKey } from "../../../utils/verifyParseKey";
-
-// Allow receive loop to run for up to 30 seconds
-// Why so long? the rate determining API call (getAvailability, getEventTypes) can take up to 15 seconds at peak times so we give it a little extra time to complete.
-export const maxDuration = 30;
-
-/**
- * Verifies email signature and app authorization,
- * then hands off to booking agent.
- */
-export const POST = async (request: NextRequest) => {
- const verified = verifyParseKey(request.url);
-
- if (!verified) {
- return new NextResponse("Unauthorized", { status: 401 });
- }
-
- const formData = await request.formData();
- const body = Object.fromEntries(formData);
- const envelope = JSON.parse(body.envelope as string);
-
- const aiEmail = envelope.to[0];
- const subject = body.subject || "";
-
- try {
- await checkRateLimitAndThrowError({
- identifier: `ai:email:${envelope.from}`,
- rateLimitingType: "ai",
- });
- } catch (error) {
- await sendEmail({
- subject: `Re: ${subject}`,
- text: "Thanks for using Cal.ai! You've reached your daily limit. Please try again tomorrow.",
- to: envelope.from,
- from: aiEmail,
- });
-
- return new NextResponse("Exceeded rate limit", { status: 200 }); // Don't return 429 to avoid triggering retry logic in SendGrid
- }
-
- // Parse email from mixed MIME type
- const parsed: ParsedMail = await simpleParser(body.email as Source);
-
- if (!parsed.text && !parsed.subject) {
- await sendEmail({
- subject: `Re: ${subject}`,
- text: "Thanks for using Cal.ai! It looks like you forgot to include a message. Please try again.",
- to: envelope.from,
- from: aiEmail,
- });
- return new NextResponse("Email missing text and subject", { status: 400 });
- }
-
- const user = await prisma.user.findUnique({
- select: {
- email: true,
- id: true,
- username: true,
- timeZone: true,
- credentials: {
- select: {
- appId: true,
- key: true,
- },
- },
- },
- where: { email: envelope.from },
- });
-
- // body.dkim looks like {@domain-com.22222222.gappssmtp.com : pass}
- const signature = (body.dkim as string).includes(" : pass");
-
- // User is not a cal.com user or is using an unverified email.
- if (!signature || !user) {
- await sendEmail({
- html: `Thanks for your interest in Cal.ai! To get started, Make sure you have a cal.com account with this email address and then install Cal.ai here: go.cal.com/ai.`,
- subject: `Re: ${subject}`,
- text: `Thanks for your interest in Cal.ai! To get started, Make sure you have a cal.com account with this email address. You can sign up for an account at: https://cal.com/signup`,
- to: envelope.from,
- from: aiEmail,
- });
-
- return new NextResponse("ok");
- }
-
- const credential = user.credentials.find((c) => c.appId === env.APP_ID)?.key;
-
- // User has not installed the app from the app store. Direct them to install it.
- if (!(credential as { apiKey: string })?.apiKey) {
- const url = env.APP_URL;
-
- await sendEmail({
- html: `Thanks for using Cal.ai! To get started, the app must be installed. Click this link to install it.`,
- subject: `Re: ${subject}`,
- text: `Thanks for using Cal.ai! To get started, the app must be installed. Click this link to install the Cal.ai app: ${url}`,
- to: envelope.from,
- from: aiEmail,
- });
-
- return new NextResponse("ok");
- }
-
- const { apiKey } = credential as { apiKey: string };
-
- // Pre-fetch data relevant to most bookings.
- const [eventTypes, availability, users] = await Promise.all([
- fetchEventTypes({
- apiKey,
- }),
- fetchAvailability({
- apiKey,
- userId: user.id,
- dateFrom: now(user.timeZone),
- dateTo: now(user.timeZone),
- }),
- extractUsers(`${parsed.text} ${parsed.subject}`),
- ]);
-
- if ("error" in availability) {
- await sendEmail({
- subject: `Re: ${subject}`,
- text: "Sorry, there was an error fetching your availability. Please try again.",
- to: user.email,
- from: aiEmail,
- });
- console.error(availability.error);
- return new NextResponse("Error fetching availability. Please try again.", { status: 400 });
- }
-
- if ("error" in eventTypes) {
- await sendEmail({
- subject: `Re: ${subject}`,
- text: "Sorry, there was an error fetching your event types. Please try again.",
- to: user.email,
- from: aiEmail,
- });
- console.error(eventTypes.error);
- return new NextResponse("Error fetching event types. Please try again.", { status: 400 });
- }
-
- const { workingHours } = availability;
-
- const appHost = getHostFromHeaders(request.headers);
-
- // Hand off to long-running agent endpoint to handle the email. (don't await)
- fetch(`${appHost}/api/agent?parseKey=${env.PARSE_KEY}`, {
- body: JSON.stringify({
- apiKey,
- userId: user.id,
- message: parsed.text || "",
- subject: parsed.subject || "",
- replyTo: aiEmail,
- user: {
- email: user.email,
- eventTypes,
- username: user.username,
- timeZone: user.timeZone,
- workingHours,
- },
- users,
- }),
- headers: {
- "Content-Type": "application/json",
- },
- method: "POST",
- });
-
- await new Promise((r) => setTimeout(r, 1000));
-
- return new NextResponse("ok");
-};
diff --git a/apps/ai/src/env.mjs b/apps/ai/src/env.mjs
deleted file mode 100644
index 2596a26643..0000000000
--- a/apps/ai/src/env.mjs
+++ /dev/null
@@ -1,47 +0,0 @@
-import { createEnv } from "@t3-oss/env-nextjs";
-import { z } from "zod";
-
-export const env = createEnv({
- /**
- * Specify your client-side environment variables schema here. This way you can ensure the app
- * isn't built with invalid env vars. To expose them to the client, prefix them with
- * `NEXT_PUBLIC_`.
- */
- client: {
- // NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
- },
-
- /**
- * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
- * middlewares) or client-side so we need to destruct manually.
- */
- runtimeEnv: {
- BACKEND_URL: process.env.BACKEND_URL,
- FRONTEND_URL: process.env.FRONTEND_URL,
- APP_ID: process.env.APP_ID,
- APP_URL: process.env.APP_URL,
- SENDER_DOMAIN: process.env.SENDER_DOMAIN,
- PARSE_KEY: process.env.PARSE_KEY,
- NODE_ENV: process.env.NODE_ENV,
- OPENAI_API_KEY: process.env.OPENAI_API_KEY,
- SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
- DATABASE_URL: process.env.DATABASE_URL,
- },
-
- /**
- * Specify your server-side environment variables schema here. This way you can ensure the app
- * isn't built with invalid env vars.
- */
- server: {
- BACKEND_URL: z.string().url(),
- FRONTEND_URL: z.string().url(),
- APP_ID: z.string().min(1),
- APP_URL: z.string().url(),
- SENDER_DOMAIN: z.string().min(1),
- PARSE_KEY: z.string().min(1),
- NODE_ENV: z.enum(["development", "test", "production"]),
- OPENAI_API_KEY: z.string().min(1),
- SENDGRID_API_KEY: z.string().min(1),
- DATABASE_URL: z.string().url(),
- },
-});
diff --git a/apps/ai/src/public/architecture.png b/apps/ai/src/public/architecture.png
deleted file mode 100644
index 52eedfe6f5..0000000000
Binary files a/apps/ai/src/public/architecture.png and /dev/null differ
diff --git a/apps/ai/src/tools/createBooking.ts b/apps/ai/src/tools/createBooking.ts
deleted file mode 100644
index 40bb4d0fc1..0000000000
--- a/apps/ai/src/tools/createBooking.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import type { UserList } from "~/src/types/user";
-
-import { env } from "../env.mjs";
-
-/**
- * Creates a booking for a user by event type, times, and timezone.
- */
-const createBooking = async ({
- apiKey,
- userId,
- users,
- eventTypeId,
- start,
- end,
- timeZone,
- language,
- invite,
-}: {
- apiKey: string;
- userId: number;
- users: UserList;
- eventTypeId: number;
- start: string;
- end: string;
- timeZone: string;
- language: string;
- invite: number;
- title?: string;
- status?: string;
-}): Promise => {
- const params = {
- apiKey,
- userId: userId.toString(),
- };
-
- const urlParams = new URLSearchParams(params);
-
- const url = `${env.BACKEND_URL}/bookings?${urlParams.toString()}`;
-
- const user = users.find((u) => u.id === invite);
-
- if (!user) {
- return { error: `User with id ${invite} not found to invite` };
- }
-
- const responses = {
- id: invite.toString(),
- name: user.username,
- email: user.email,
- };
-
- const response = await fetch(url, {
- body: JSON.stringify({
- end,
- eventTypeId,
- language,
- metadata: {},
- responses,
- start,
- timeZone,
- }),
- headers: {
- "Content-Type": "application/json",
- },
- method: "POST",
- });
-
- // Let GPT handle this. This will happen when wrong event type id is used.
- // if (response.status === 401) throw new Error("Unauthorized");
-
- const data = await response.json();
-
- if (response.status !== 200) {
- return {
- error: data.message,
- };
- }
-
- return "Booking created";
-};
-
-const createBookingTool = (apiKey: string, userId: number, users: UserList) => {
- return new DynamicStructuredTool({
- description: "Creates a booking on the primary user's calendar.",
- func: async ({ eventTypeId, start, end, timeZone, language, invite, title, status }) => {
- return JSON.stringify(
- await createBooking({
- apiKey,
- userId,
- users,
- end,
- eventTypeId,
- language,
- invite,
- start,
- status,
- timeZone,
- title,
- })
- );
- },
- name: "createBooking",
- schema: z.object({
- end: z
- .string()
- .describe("This should correspond to the event type's length, unless otherwise specified."),
- eventTypeId: z.number(),
- language: z.string(),
- invite: z.number().describe("External user id to invite."),
- start: z.string(),
- status: z.string().optional().describe("ACCEPTED, PENDING, CANCELLED or REJECTED"),
- timeZone: z.string(),
- title: z.string().optional(),
- }),
- });
-};
-
-export default createBookingTool;
diff --git a/apps/ai/src/tools/deleteBooking.ts b/apps/ai/src/tools/deleteBooking.ts
deleted file mode 100644
index 981cef8f58..0000000000
--- a/apps/ai/src/tools/deleteBooking.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import { env } from "../env.mjs";
-
-/**
- * Cancels a booking for a user by ID with reason.
- */
-const cancelBooking = async ({
- apiKey,
- id,
- reason,
-}: {
- apiKey: string;
- id: string;
- reason: string;
-}): Promise => {
- const params = {
- apiKey,
- };
-
- const urlParams = new URLSearchParams(params);
-
- const url = `${env.BACKEND_URL}/bookings/${id}/cancel?${urlParams.toString()}`;
-
- const response = await fetch(url, {
- body: JSON.stringify({ reason }),
- headers: {
- "Content-Type": "application/json",
- },
- method: "DELETE",
- });
-
- // Let GPT handle this. This will happen when wrong booking id is used.
- // if (response.status === 401) throw new Error("Unauthorized");
-
- const data = await response.json();
-
- if (response.status !== 200) {
- return { error: data.message };
- }
-
- return "Booking cancelled";
-};
-
-const cancelBookingTool = (apiKey: string) => {
- return new DynamicStructuredTool({
- description: "Cancel a booking",
- func: async ({ id, reason }) => {
- return JSON.stringify(
- await cancelBooking({
- apiKey,
- id,
- reason,
- })
- );
- },
- name: "cancelBooking",
- schema: z.object({
- id: z.string(),
- reason: z.string(),
- }),
- });
-};
-
-export default cancelBookingTool;
diff --git a/apps/ai/src/tools/getAvailability.ts b/apps/ai/src/tools/getAvailability.ts
deleted file mode 100644
index 26258e0c60..0000000000
--- a/apps/ai/src/tools/getAvailability.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import { env } from "../env.mjs";
-import type { Availability } from "../types/availability";
-
-/**
- * Fetches availability for a user by date range and event type.
- */
-export const fetchAvailability = async ({
- apiKey,
- userId,
- dateFrom,
- dateTo,
-}: {
- apiKey: string;
- userId: number;
- dateFrom: string;
- dateTo: string;
-}): Promise | { error: string }> => {
- const params: { [k: string]: string } = {
- apiKey,
- userId: userId.toString(),
- dateFrom,
- dateTo,
- };
-
- const urlParams = new URLSearchParams(params);
-
- const url = `${env.BACKEND_URL}/availability?${urlParams.toString()}`;
-
- const response = await fetch(url);
-
- if (response.status === 401) throw new Error("Unauthorized");
-
- const data = await response.json();
-
- if (response.status !== 200) {
- return { error: data.message };
- }
-
- return {
- busy: data.busy,
- dateRanges: data.dateRanges,
- timeZone: data.timeZone,
- workingHours: data.workingHours,
- };
-};
-
-const getAvailabilityTool = (apiKey: string) => {
- return new DynamicStructuredTool({
- description: "Get availability of users within range.",
- func: async ({ userIds, dateFrom, dateTo }) => {
- return JSON.stringify(
- await Promise.all(
- userIds.map(
- async (userId) =>
- await fetchAvailability({
- userId: userId,
- apiKey,
- dateFrom,
- dateTo,
- })
- )
- )
- );
- },
- name: "getAvailability",
- schema: z.object({
- userIds: z.array(z.number()).describe("The users to fetch availability for."),
- dateFrom: z.string(),
- dateTo: z.string(),
- }),
- });
-};
-
-export default getAvailabilityTool;
diff --git a/apps/ai/src/tools/getBookings.ts b/apps/ai/src/tools/getBookings.ts
deleted file mode 100644
index 6976f31ecf..0000000000
--- a/apps/ai/src/tools/getBookings.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import { env } from "../env.mjs";
-import type { Booking } from "../types/booking";
-import { BOOKING_STATUS } from "../types/booking";
-
-/**
- * Fetches bookings for a user by date range.
- */
-const fetchBookings = async ({
- apiKey,
- userId,
- from,
- to,
-}: {
- apiKey: string;
- userId: number;
- from: string;
- to: string;
-}): Promise => {
- const params = {
- apiKey,
- userId: userId.toString(),
- dateFrom: from,
- dateTo: to,
- };
-
- const urlParams = new URLSearchParams(params);
-
- const url = `${env.BACKEND_URL}/bookings?${urlParams.toString()}`;
-
- const response = await fetch(url);
-
- if (response.status === 401) throw new Error("Unauthorized");
-
- const data = await response.json();
-
- if (response.status !== 200) {
- return { error: data.message };
- }
-
- const bookings: Booking[] = data.bookings
- .filter((booking: Booking) => {
- const notCancelled = booking.status !== BOOKING_STATUS.CANCELLED;
-
- return notCancelled;
- })
- .map(({ endTime, eventTypeId, id, startTime, status, title }: Booking) => ({
- endTime,
- eventTypeId,
- id,
- startTime,
- status,
- title,
- }));
-
- return bookings;
-};
-
-const getBookingsTool = (apiKey: string, userId: number) => {
- return new DynamicStructuredTool({
- description: "Get bookings for the primary user between two dates.",
- func: async ({ from, to }) => {
- return JSON.stringify(await fetchBookings({ apiKey, userId, from, to }));
- },
- name: "getBookings",
- schema: z.object({
- from: z.string().describe("ISO 8601 datetime string"),
- to: z.string().describe("ISO 8601 datetime string"),
- }),
- });
-};
-
-export default getBookingsTool;
diff --git a/apps/ai/src/tools/getEventTypes.ts b/apps/ai/src/tools/getEventTypes.ts
deleted file mode 100644
index c59df69c99..0000000000
--- a/apps/ai/src/tools/getEventTypes.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import { env } from "../env.mjs";
-import type { EventType } from "../types/eventType";
-
-/**
- * Fetches event types by user ID.
- */
-export const fetchEventTypes = async ({ apiKey, userId }: { apiKey: string; userId?: number }) => {
- const params: Record = {
- apiKey,
- };
-
- if (userId) {
- params["userId"] = userId.toString();
- }
-
- const urlParams = new URLSearchParams(params);
-
- const url = `${env.BACKEND_URL}/event-types?${urlParams.toString()}`;
-
- const response = await fetch(url);
-
- if (response.status === 401) throw new Error("Unauthorized");
-
- const data = await response.json();
-
- if (response.status !== 200) {
- return { error: data.message };
- }
-
- return data.event_types.map((eventType: EventType) => ({
- id: eventType.id,
- slug: eventType.slug,
- length: eventType.length,
- title: eventType.title,
- }));
-};
-
-const getEventTypesTool = (apiKey: string) => {
- return new DynamicStructuredTool({
- description: "Get a user's event type IDs. Usually necessary to book a meeting.",
- func: async ({ userId }) => {
- return JSON.stringify(
- await fetchEventTypes({
- apiKey,
- userId,
- })
- );
- },
- name: "getEventTypes",
- schema: z.object({
- userId: z.number().optional().describe("The user ID. Defaults to the primary user's ID."),
- }),
- });
-};
-
-export default getEventTypesTool;
diff --git a/apps/ai/src/tools/sendBookingEmail.ts b/apps/ai/src/tools/sendBookingEmail.ts
deleted file mode 100644
index 2133f61769..0000000000
--- a/apps/ai/src/tools/sendBookingEmail.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import { env } from "~/src/env.mjs";
-import type { User, UserList } from "~/src/types/user";
-import sendEmail from "~/src/utils/sendEmail";
-
-export const sendBookingEmail = async ({
- user,
- agentEmail,
- subject,
- to,
- message,
- eventTypeSlug,
- slots,
- date,
-}: {
- apiKey: string;
- user: User;
- users: UserList;
- agentEmail: string;
- subject: string;
- to: string;
- message: string;
- eventTypeSlug: string;
- slots?: {
- time: string;
- text: string;
- }[];
- date: {
- date: string;
- text: string;
- };
-}) => {
- // const url = `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?date=${date}`;
- const timeUrls = slots?.map(({ time, text }) => {
- return {
- url: `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?slot=${time}`,
- text,
- };
- });
-
- const dateUrl = {
- url: `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?date=${date.date}`,
- text: date.text,
- };
-
- await sendEmail({
- subject,
- to,
- cc: user.email,
- from: agentEmail,
- text: message
- .split("[[[Slots]]]")
- .join(timeUrls?.map(({ url, text }) => `${text}: ${url}`).join("\n"))
- .split("[[[Link]]]")
- .join(`${dateUrl.text}: ${dateUrl.url}`),
- html: message
- .split("\n")
- .join("
")
- .split("[[[Slots]]]")
- .join(timeUrls?.map(({ url, text }) => `${text}`).join("
"))
- .split("[[[Link]]]")
- .join(`${dateUrl.text}`),
- });
-
- return "Booking link sent";
-};
-
-const sendBookingEmailTool = (apiKey: string, user: User, users: UserList, agentEmail: string) => {
- return new DynamicStructuredTool({
- description:
- "Send a booking link via email. Useful for scheduling with non cal users. Be confident, suggesting a good date/time with a fallback to a link to select a date/time.",
- func: async ({ message, subject, to, eventTypeSlug, slots, date }) => {
- return JSON.stringify(
- await sendBookingEmail({
- apiKey,
- user,
- users,
- agentEmail,
- subject,
- to,
- message,
- eventTypeSlug,
- slots,
- date,
- })
- );
- },
- name: "sendBookingEmail",
-
- schema: z.object({
- message: z
- .string()
- .describe(
- "A polite and professional email with an intro and signature at the end. Specify you are the AI booking assistant of the primary user. Use [[[Slots]]] and a fallback [[[Link]]] to inject good times and 'see all times' into messages"
- ),
- subject: z.string(),
- to: z
- .string()
- .describe("email address to send the booking link to. Primary user is automatically CC'd"),
- eventTypeSlug: z.string().describe("the slug of the event type to book"),
- slots: z
- .array(
- z.object({
- time: z.string().describe("YYYY-MM-DDTHH:mm in UTC"),
- text: z.string().describe("minimum readable label. Ex. 4pm."),
- })
- )
- .optional()
- .describe("Time slots the external user can click"),
- date: z
- .object({
- date: z.string().describe("YYYY-MM-DD"),
- text: z.string().describe('"See all times" or similar'),
- })
- .describe(
- "A booking link that allows the external user to select a date / time. Should be a fallback to time slots"
- ),
- }),
- });
-};
-
-export default sendBookingEmailTool;
diff --git a/apps/ai/src/tools/updateBooking.ts b/apps/ai/src/tools/updateBooking.ts
deleted file mode 100644
index eec462e897..0000000000
--- a/apps/ai/src/tools/updateBooking.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { DynamicStructuredTool } from "@langchain/core/tools";
-import { z } from "zod";
-
-import { env } from "../env.mjs";
-
-/**
- * Edits a booking for a user by booking ID with new times, title, description, or status.
- */
-const editBooking = async ({
- apiKey,
- userId,
- id,
- startTime, // In the docs it says start, but it's startTime: https://cal.com/docs/enterprise-features/api/api-reference/bookings#edit-an-existing-booking.
- endTime, // Same here: it says end but it's endTime.
- title,
- description,
- status,
-}: {
- apiKey: string;
- userId: number;
- id: string;
- startTime?: string;
- endTime?: string;
- title?: string;
- description?: string;
- status?: string;
-}): Promise => {
- const params = {
- apiKey,
- userId: userId.toString(),
- };
- const urlParams = new URLSearchParams(params);
-
- const url = `${env.BACKEND_URL}/bookings/${id}?${urlParams.toString()}`;
-
- const response = await fetch(url, {
- body: JSON.stringify({ description, endTime, startTime, status, title }),
- headers: {
- "Content-Type": "application/json",
- },
- method: "PATCH",
- });
-
- // Let GPT handle this. This will happen when wrong booking id is used.
- // if (response.status === 401) throw new Error("Unauthorized");
-
- const data = await response.json();
-
- if (response.status !== 200) {
- return { error: data.message };
- }
-
- return "Booking edited";
-};
-
-const editBookingTool = (apiKey: string, userId: number) => {
- return new DynamicStructuredTool({
- description: "Edit a booking",
- func: async ({ description, endTime, id, startTime, status, title }) => {
- return JSON.stringify(
- await editBooking({
- apiKey,
- userId,
- description,
- endTime,
- id,
- startTime,
- status,
- title,
- })
- );
- },
- name: "editBooking",
- schema: z.object({
- description: z.string().optional(),
- endTime: z.string().optional(),
- id: z.string(),
- startTime: z.string().optional(),
- status: z.string().optional(),
- title: z.string().optional(),
- }),
- });
-};
-
-export default editBookingTool;
diff --git a/apps/ai/src/types/availability.ts b/apps/ai/src/types/availability.ts
deleted file mode 100644
index 62ed3b5ed5..0000000000
--- a/apps/ai/src/types/availability.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-export type Availability = {
- busy: {
- start: string;
- end: string;
- title?: string;
- }[];
- timeZone: string;
- dateRanges: {
- start: string;
- end: string;
- }[];
- workingHours: {
- days: number[];
- startTime: number;
- endTime: number;
- userId: number;
- }[];
- dateOverrides: {
- date: string;
- startTime: number;
- endTime: number;
- userId: number;
- };
- currentSeats: number;
-};
diff --git a/apps/ai/src/types/booking.ts b/apps/ai/src/types/booking.ts
deleted file mode 100644
index 91eaf4e322..0000000000
--- a/apps/ai/src/types/booking.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export enum BOOKING_STATUS {
- ACCEPTED = "ACCEPTED",
- PENDING = "PENDING",
- CANCELLED = "CANCELLED",
- REJECTED = "REJECTED",
-}
-
-export type Booking = {
- id: number;
- userId: number;
- description: string | null;
- eventTypeId: number;
- uid: string;
- title: string;
- startTime: string;
- endTime: string;
- attendees: { email: string; name: string; timeZone: string; locale: string }[] | null;
- user: { email: string; name: string; timeZone: string; locale: string }[] | null;
- payment: { id: number; success: boolean; paymentOption: string }[];
- metadata: object | null;
- status: BOOKING_STATUS;
- responses: { email: string; name: string; location: string } | null;
-};
diff --git a/apps/ai/src/types/eventType.ts b/apps/ai/src/types/eventType.ts
deleted file mode 100644
index b9de4cc230..0000000000
--- a/apps/ai/src/types/eventType.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export type EventType = {
- id: number;
- title: string;
- length: number;
- metadata: object;
- slug: string;
- hosts: {
- userId: number;
- isFixed: boolean;
- }[];
- hidden: boolean;
- // ...
-};
diff --git a/apps/ai/src/types/user.ts b/apps/ai/src/types/user.ts
deleted file mode 100644
index e39b1b234b..0000000000
--- a/apps/ai/src/types/user.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { EventType } from "./eventType";
-import type { WorkingHours } from "./workingHours";
-
-export type User = {
- id: number;
- email: string;
- username: string;
- timeZone: string;
- eventTypes: EventType[];
- workingHours: WorkingHours[];
-};
-
-export type UserList = {
- id?: number;
- email?: string;
- username?: string;
- type: "fromUsername" | "fromEmail";
-}[];
diff --git a/apps/ai/src/types/workingHours.ts b/apps/ai/src/types/workingHours.ts
deleted file mode 100644
index e3a1905a45..0000000000
--- a/apps/ai/src/types/workingHours.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type WorkingHours = {
- days: number[];
- startTime: number;
- endTime: number;
-};
diff --git a/apps/ai/src/utils/agent.ts b/apps/ai/src/utils/agent.ts
deleted file mode 100644
index 4ef72f10cc..0000000000
--- a/apps/ai/src/utils/agent.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { ChatPromptTemplate } from "@langchain/core/prompts";
-import { ChatOpenAI } from "@langchain/openai";
-import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
-
-import { env } from "../env.mjs";
-import createBookingIfAvailable from "../tools/createBooking";
-import deleteBooking from "../tools/deleteBooking";
-import getAvailability from "../tools/getAvailability";
-import getBookings from "../tools/getBookings";
-import sendBookingEmail from "../tools/sendBookingEmail";
-import updateBooking from "../tools/updateBooking";
-import type { EventType } from "../types/eventType";
-import type { User, UserList } from "../types/user";
-import type { WorkingHours } from "../types/workingHours";
-import now from "./now";
-
-const gptModel = "gpt-4-0125-preview";
-
-/**
- * Core of the Cal.ai booking agent: a LangChain Agent Executor.
- * Uses a toolchain to book meetings, list available slots, etc.
- * Uses OpenAI functions to better enforce JSON-parsable output from the LLM.
- */
-const agent = async (
- input: string,
- user: User,
- users: UserList,
- apiKey: string,
- userId: number,
- agentEmail: string
-) => {
- const tools = [
- // getEventTypes(apiKey),
- getAvailability(apiKey),
- getBookings(apiKey, userId),
- createBookingIfAvailable(apiKey, userId, users),
- updateBooking(apiKey, userId),
- deleteBooking(apiKey),
- sendBookingEmail(apiKey, user, users, agentEmail),
- ];
-
- const model = new ChatOpenAI({
- modelName: gptModel,
- openAIApiKey: env.OPENAI_API_KEY,
- temperature: 0,
- });
-
- /**
- * Initialize the agent executor with arguments.
- */
- const prompt =
- ChatPromptTemplate.fromTemplate(`You are Cal.ai - a bleeding edge scheduling assistant that interfaces via email.
- Make sure your final answers are definitive, complete and well formatted.
- Sometimes, tools return errors. In this case, try to handle the error intelligently or ask the user for more information.
- Tools will always handle times in UTC, but times sent to users should be formatted per that user's timezone.
- In responses to users, always summarize necessary context and open the door to follow ups. For example "I have booked your chat with @username for 3pm on Wednesday, December 20th, 2023 EST. Please let me know if you need to reschedule."
- If you can't find a referenced user, ask the user for their email or @username. Make sure to specify that usernames require the @username format. Users don't know other users' userIds.
-
- The primary user's id is: ${userId}
- The primary user's username is: ${user.username}
- The current time in the primary user's timezone is: ${now(user.timeZone, {
- weekday: "long",
- year: "numeric",
- month: "long",
- day: "numeric",
- hour: "numeric",
- minute: "numeric",
- })}
- The primary user's time zone is: ${user.timeZone}
- The primary user's event types are: ${user.eventTypes
- .map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
- .join("\n")}
- The primary user's working hours are: ${user.workingHours
- .map(
- (w: WorkingHours) =>
- `Days: ${w.days.join(", ")}, Start Time (minutes in UTC): ${
- w.startTime
- }, End Time (minutes in UTC): ${w.endTime};`
- )
- .join("\n")}
- ${
- users.length
- ? `The email references the following @usernames and emails: ${users
- .map(
- (u) =>
- `${
- (u.id ? `, id: ${u.id}` : "id: (non user)") +
- (u.username
- ? u.type === "fromUsername"
- ? `, username: @${u.username}`
- : ", username: REDACTED"
- : ", (no username)") +
- (u.email
- ? u.type === "fromEmail"
- ? `, email: ${u.email}`
- : ", email: REDACTED"
- : ", (no email)")
- };`
- )
- .join("\n")}`
- : ""
- }`);
- const agent = await createOpenAIFunctionsAgent({
- llm: model,
- prompt,
- tools,
- });
-
- const executor = new AgentExecutor({
- agent,
- tools,
- returnIntermediateSteps: env.NODE_ENV === "development",
- verbose: env.NODE_ENV === "development",
- });
-
- const result = await executor.invoke({ input });
- const { output } = result;
-
- return output;
-};
-
-export default agent;
diff --git a/apps/ai/src/utils/context.ts b/apps/ai/src/utils/context.ts
deleted file mode 100644
index 26864e73d6..0000000000
--- a/apps/ai/src/utils/context.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const context = { apiKey: "", userId: "" };
diff --git a/apps/ai/src/utils/extractUsers.ts b/apps/ai/src/utils/extractUsers.ts
deleted file mode 100644
index 10a6f9fe84..0000000000
--- a/apps/ai/src/utils/extractUsers.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import prisma from "@calcom/prisma";
-
-import type { UserList } from "../types/user";
-
-/*
- * Extracts usernames (@Example) and emails (hi@example.com) from a string
- */
-export const extractUsers = async (text: string) => {
- const usernames = text
- .match(/(? username.slice(1).toLowerCase());
- const emails = text
- .match(/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/g)
- ?.map((email) => email.toLowerCase());
-
- const dbUsersFromUsernames = usernames
- ? await prisma.user.findMany({
- select: {
- id: true,
- username: true,
- email: true,
- },
- where: {
- username: {
- in: usernames,
- },
- },
- })
- : [];
-
- const usersFromUsernames = usernames
- ? usernames.map((username) => {
- const user = dbUsersFromUsernames.find((u) => u.username === username);
- return user
- ? {
- username,
- id: user.id,
- email: user.email,
- type: "fromUsername",
- }
- : {
- username,
- id: null,
- email: null,
- type: "fromUsername",
- };
- })
- : [];
-
- const dbUsersFromEmails = emails
- ? await prisma.user.findMany({
- select: {
- id: true,
- email: true,
- username: true,
- },
- where: {
- email: {
- in: emails,
- },
- },
- })
- : [];
-
- const usersFromEmails = emails
- ? emails.map((email) => {
- const user = dbUsersFromEmails.find((u) => u.email === email);
- return user
- ? {
- email,
- id: user.id,
- username: user.username,
- type: "fromEmail",
- }
- : {
- email,
- id: null,
- username: null,
- type: "fromEmail",
- };
- })
- : [];
-
- return [...usersFromUsernames, ...usersFromEmails] as UserList;
-};
diff --git a/apps/ai/src/utils/host.ts b/apps/ai/src/utils/host.ts
deleted file mode 100644
index f215d7e7d0..0000000000
--- a/apps/ai/src/utils/host.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { NextRequest } from "next/server";
-
-const getHostFromHeaders = (headers: NextRequest["headers"]): string => {
- return `https://${headers.get("host")}`;
-};
-
-export default getHostFromHeaders;
diff --git a/apps/ai/src/utils/now.ts b/apps/ai/src/utils/now.ts
deleted file mode 100644
index 98630d7bc4..0000000000
--- a/apps/ai/src/utils/now.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export default function now(timeZone: string, options: Intl.DateTimeFormatOptions = {}) {
- return new Date().toLocaleString("en-US", {
- timeZone,
- ...options,
- });
-}
diff --git a/apps/ai/src/utils/sendEmail.ts b/apps/ai/src/utils/sendEmail.ts
deleted file mode 100644
index adbc9693e7..0000000000
--- a/apps/ai/src/utils/sendEmail.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import mail from "@sendgrid/mail";
-
-const sendgridAPIKey = process.env.SENDGRID_API_KEY as string;
-
-/**
- * Simply send an email by address, subject, and body.
- */
-const send = async ({
- subject,
- to,
- cc,
- from,
- text,
- html,
-}: {
- subject: string;
- to: string | string[];
- cc?: string | string[];
- from: string;
- text: string;
- html?: string;
-}): Promise => {
- mail.setApiKey(sendgridAPIKey);
-
- const msg = {
- to,
- cc,
- from: {
- email: from,
- name: "Cal.ai",
- },
- text,
- html,
- subject,
- };
-
- const res = await mail.send(msg);
- const success = !!res;
-
- return success;
-};
-
-export default send;
diff --git a/apps/ai/src/utils/verifyParseKey.ts b/apps/ai/src/utils/verifyParseKey.ts
deleted file mode 100644
index 5ac81ed78c..0000000000
--- a/apps/ai/src/utils/verifyParseKey.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { NextRequest } from "next/server";
-
-import { env } from "../env.mjs";
-
-/**
- * Verifies that the request contains the correct parse key.
- * env.PARSE_KEY must be configured as a query param in the sendgrid inbound parse settings.
- */
-export const verifyParseKey = (url: NextRequest["url"]) => {
- const verified = new URL(url).searchParams.get("parseKey") === env.PARSE_KEY;
-
- return verified;
-};
diff --git a/apps/ai/tsconfig.json b/apps/ai/tsconfig.json
deleted file mode 100644
index aae1b4e758..0000000000
--- a/apps/ai/tsconfig.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "extends": "@calcom/tsconfig/nextjs.json",
- "compilerOptions": {
- "strict": true,
- "jsx": "preserve",
- "baseUrl": ".",
- "paths": {
- "~/*": ["*"]
- },
- "plugins": [
- {
- "name": "next"
- }
- ]
- },
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts",
- "../../packages/types/window.d.ts"
- ],
- "exclude": ["node_modules"]
-}
diff --git a/packages/app-store/apps.browser.generated.tsx b/packages/app-store/apps.browser.generated.tsx
index 4fea31051f..e623dbc919 100644
--- a/packages/app-store/apps.browser.generated.tsx
+++ b/packages/app-store/apps.browser.generated.tsx
@@ -11,7 +11,6 @@ export const InstallAppButtonMap = {
vital: dynamic(() => import("./vital/components/InstallAppButton")),
};
export const AppSettingsComponentsMap = {
- "cal-ai": dynamic(() => import("./cal-ai/components/AppSettingsInterface")),
"general-app-settings": dynamic(() =>
import("./templates/general-app-settings/components/AppSettingsInterface")
),
diff --git a/packages/app-store/apps.metadata.generated.ts b/packages/app-store/apps.metadata.generated.ts
index 5023ca070e..09d7ae9954 100644
--- a/packages/app-store/apps.metadata.generated.ts
+++ b/packages/app-store/apps.metadata.generated.ts
@@ -9,7 +9,6 @@ import around_config_json from "./around/config.json";
import autocheckin_config_json from "./autocheckin/config.json";
import baa_for_hipaa_config_json from "./baa-for-hipaa/config.json";
import basecamp3_config_json from "./basecamp3/config.json";
-import cal_ai_config_json from "./cal-ai/config.json";
import { metadata as caldavcalendar__metadata_ts } from "./caldavcalendar/_metadata";
import campfire_config_json from "./campfire/config.json";
import campsite_config_json from "./campsite/config.json";
@@ -102,7 +101,6 @@ export const appStoreMetadata = {
autocheckin: autocheckin_config_json,
"baa-for-hipaa": baa_for_hipaa_config_json,
basecamp3: basecamp3_config_json,
- "cal-ai": cal_ai_config_json,
caldavcalendar: caldavcalendar__metadata_ts,
campfire: campfire_config_json,
campsite: campsite_config_json,
diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts
index 6396cd1ca3..7c143a2d30 100644
--- a/packages/app-store/apps.server.generated.ts
+++ b/packages/app-store/apps.server.generated.ts
@@ -10,7 +10,6 @@ export const apiHandlers = {
autocheckin: import("./autocheckin/api"),
"baa-for-hipaa": import("./baa-for-hipaa/api"),
basecamp3: import("./basecamp3/api"),
- "cal-ai": import("./cal-ai/api"),
caldavcalendar: import("./caldavcalendar/api"),
campfire: import("./campfire/api"),
campsite: import("./campsite/api"),
diff --git a/packages/app-store/cal-ai/DESCRIPTION.md b/packages/app-store/cal-ai/DESCRIPTION.md
deleted file mode 100644
index 6c045ee7c0..0000000000
--- a/packages/app-store/cal-ai/DESCRIPTION.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-items:
- - 1.jpg
- - 2.jpg
- - 3.jpg
- - 4.jpg
- - 5.jpg
----
-
-{DESCRIPTION}
-
-## Example questions:
-
-- "Book a meeting with @cal-user this week at a good time"
-- "Find a time to meet with not-a-cal-user@gmail.com this week"
-- "Move my meeting with [Someone's Name] to next week"
-- "What does my week look like?"
-- "When is my next meeting?"
-- "What is my least busy day next week?"
-- "Cancel the meeting with [Someone's Name]"
-- "Reschedule the meeting with not-a-cal-user@gmail.com"
-- "Is @cal-user busy on Friday at noon?"
-- "Move my meeting at 2pm tomorrow to next week on Friday at 5pm"
-
-### Or, any of that in a thread:
-- What meetings do I have this week?
- - Meeting 1, Meeting 2
-- Cancel that first meeting
-
-
-### Or, doing multiple things:
-- Cancel all my meetings tomorrow then book a quick chat with @joe at noon, a long meeting with @jane at 1 (both tomorrow). Also see if Toby is down to grab dinner this week (toby@gmail.com). Finally send me my updated agenda of everything I have coming up in the next couple days.
\ No newline at end of file
diff --git a/packages/app-store/cal-ai/api/_getAdd.ts b/packages/app-store/cal-ai/api/_getAdd.ts
deleted file mode 100644
index d8e13972c6..0000000000
--- a/packages/app-store/cal-ai/api/_getAdd.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-
-import { defaultResponder } from "@calcom/lib/server";
-
-import checkSession from "../../_utils/auth";
-import { withPaidAppRedirect } from "../../_utils/paid-apps";
-import appConfig from "../config.json";
-
-export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
- const session = checkSession(req);
-
- const redirectUrl = await withPaidAppRedirect({
- appPaidMode: appConfig.paid.mode,
- appSlug: appConfig.slug,
- userId: session.user.id,
- priceId: appConfig.paid.priceId,
- });
-
- if (!redirectUrl) {
- return res.status(500).json({ message: "Failed to create Stripe checkout session" });
- }
-
- return { url: redirectUrl };
-}
-
-export default defaultResponder(getHandler);
diff --git a/packages/app-store/cal-ai/api/_getCallback.ts b/packages/app-store/cal-ai/api/_getCallback.ts
deleted file mode 100644
index e424d9bde7..0000000000
--- a/packages/app-store/cal-ai/api/_getCallback.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from "next";
-
-import { defaultResponder } from "@calcom/lib/server";
-import { createContext } from "@calcom/trpc/server/createContext";
-import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router";
-
-import checkSession from "../../_utils/auth";
-import getInstalledAppPath from "../../_utils/getInstalledAppPath";
-import { checkInstalled, createDefaultInstallation } from "../../_utils/installation";
-import { withStripeCallback } from "../../_utils/paid-apps";
-import appConfig from "../config.json";
-
-export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
- const session = checkSession(req);
- const slug = appConfig.slug;
- const appType = appConfig.type;
-
- const { checkoutId } = req.query as { checkoutId: string };
- if (!checkoutId) {
- return { url: `/apps/installed?error=${JSON.stringify({ message: "No Stripe Checkout Session ID" })}` };
- }
-
- const { url } = await withStripeCallback(checkoutId, slug, async ({ checkoutSession }) => {
- const ctx = await createContext({ req, res });
- const caller = apiKeysRouter.createCaller(ctx);
-
- const apiKey = await caller.create({
- note: "Cal.ai",
- expiresAt: null,
- appId: "cal-ai",
- });
-
- await checkInstalled(slug, session.user.id);
- await createDefaultInstallation({
- appType,
- user: session.user,
- slug,
- key: {
- apiKey,
- },
- subscriptionId: checkoutSession.subscription?.toString(),
- billingCycleStart: new Date().getDate(),
- paymentStatus: "active",
- });
-
- await fetch(
- `${process.env.NODE_ENV === "development" ? "http://localhost:3005" : "https://cal.ai"}/api/onboard`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- userId: session.user.id,
- }),
- }
- );
-
- return { url: getInstalledAppPath({ variant: appConfig.variant, slug: "cal-ai" }) };
- });
-
- return res.redirect(url);
-}
-
-export default defaultResponder(getHandler);
diff --git a/packages/app-store/cal-ai/api/add.ts b/packages/app-store/cal-ai/api/add.ts
deleted file mode 100644
index 912126e232..0000000000
--- a/packages/app-store/cal-ai/api/add.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { defaultHandler } from "@calcom/lib/server";
-
-export default defaultHandler({
- GET: import("./_getAdd"),
-});
diff --git a/packages/app-store/cal-ai/api/callback.ts b/packages/app-store/cal-ai/api/callback.ts
deleted file mode 100644
index 07586cf85f..0000000000
--- a/packages/app-store/cal-ai/api/callback.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { defaultHandler } from "@calcom/lib/server";
-
-export default defaultHandler({
- GET: import("./_getCallback"),
-});
diff --git a/packages/app-store/cal-ai/api/index.ts b/packages/app-store/cal-ai/api/index.ts
deleted file mode 100644
index eb12c1b4ed..0000000000
--- a/packages/app-store/cal-ai/api/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as add } from "./add";
-export { default as callback } from "./callback";
diff --git a/packages/app-store/cal-ai/components/AppSettingsInterface.tsx b/packages/app-store/cal-ai/components/AppSettingsInterface.tsx
deleted file mode 100644
index 8c40985f6e..0000000000
--- a/packages/app-store/cal-ai/components/AppSettingsInterface.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { useSession } from "next-auth/react";
-
-export function MailLink({ subject, body }: { subject: string; body?: string }) {
- const { data } = useSession();
-
- return (
-
- {subject ? subject : body}
-
- );
-}
-export default function AppSettings() {
- return (
- <>
-
-
Example questions:
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
-
-
Or, any of that in a thread:
-
- -
- What meetings do I have this week?
-
-
- - Cancel that first meeting
-
-
Or, doing multiple things:
-
-
- >
- );
-}
diff --git a/packages/app-store/cal-ai/config.json b/packages/app-store/cal-ai/config.json
deleted file mode 100644
index c13c045312..0000000000
--- a/packages/app-store/cal-ai/config.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "/*": "Don't modify slug - If required, do it using cli edit command",
- "name": "Cal.ai",
- "slug": "cal-ai",
- "type": "cal-ai_automation",
- "logo": "icon.png",
- "url": "https://cal.ai",
- "variant": "automation",
- "categories": ["automation"],
- "publisher": "Cal.com, Inc.",
- "email": "support@cal.com",
- "description": "Cal.ai is your AI scheduling assistant. Get your personal email assistant (username@cal.ai) that you can forward emails to or have a conversation with. Cal.ai will automatically schedule meetings for you.",
- "isTemplate": false,
- "__createdUsingCli": true,
- "__template": "basic",
- "dirName": "cal-ai",
- "isOAuth": false,
- "paid": {
- "priceInUsd": 8,
- "priceId": "price_1O1ziDH8UDiwIftkDHp3MCTP",
- "mode": "subscription"
- }
-}
diff --git a/packages/app-store/cal-ai/index.ts b/packages/app-store/cal-ai/index.ts
deleted file mode 100644
index d7f3602204..0000000000
--- a/packages/app-store/cal-ai/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * as api from "./api";
diff --git a/packages/app-store/cal-ai/package.json b/packages/app-store/cal-ai/package.json
deleted file mode 100644
index 3cde71d537..0000000000
--- a/packages/app-store/cal-ai/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "https://json.schemastore.org/package.json",
- "private": true,
- "name": "@calcom/cal-ai",
- "version": "0.0.0",
- "main": "./index.ts",
- "dependencies": {
- "@calcom/lib": "*"
- },
- "devDependencies": {
- "@calcom/types": "*"
- },
- "description": "Cal.ai is your AI scheduling assistant. Get your personal email assistant (username@cal.ai) that you can forward emails to or have a conversation with. Cal.ai will automatically schedule meetings for you."
-}
diff --git a/packages/app-store/cal-ai/static/1.jpg b/packages/app-store/cal-ai/static/1.jpg
deleted file mode 100644
index 9792aedc37..0000000000
Binary files a/packages/app-store/cal-ai/static/1.jpg and /dev/null differ
diff --git a/packages/app-store/cal-ai/static/2.jpg b/packages/app-store/cal-ai/static/2.jpg
deleted file mode 100644
index 050acf433f..0000000000
Binary files a/packages/app-store/cal-ai/static/2.jpg and /dev/null differ
diff --git a/packages/app-store/cal-ai/static/3.jpg b/packages/app-store/cal-ai/static/3.jpg
deleted file mode 100644
index fab5b1ef18..0000000000
Binary files a/packages/app-store/cal-ai/static/3.jpg and /dev/null differ
diff --git a/packages/app-store/cal-ai/static/4.jpg b/packages/app-store/cal-ai/static/4.jpg
deleted file mode 100644
index 0b0fbbfde9..0000000000
Binary files a/packages/app-store/cal-ai/static/4.jpg and /dev/null differ
diff --git a/packages/app-store/cal-ai/static/5.jpg b/packages/app-store/cal-ai/static/5.jpg
deleted file mode 100644
index ccb8475bdd..0000000000
Binary files a/packages/app-store/cal-ai/static/5.jpg and /dev/null differ
diff --git a/packages/app-store/cal-ai/static/icon.png b/packages/app-store/cal-ai/static/icon.png
deleted file mode 100644
index c63758d416..0000000000
Binary files a/packages/app-store/cal-ai/static/icon.png and /dev/null differ