-
Notifications
You must be signed in to change notification settings - Fork 11.6k
feat: support auto create agent #23493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,92 @@ | ||||||||||
| const styleGuardrails = `## Style Guardrails | ||||||||||
| Be Concise: Respond succinctly, addressing one topic at most. | ||||||||||
| Embrace Variety: Use diverse language and rephrasing to enhance clarity without repeating content. | ||||||||||
| Be Conversational: Use everyday language, making the chat feel like talking to a friend. | ||||||||||
| Be Proactive: Lead the conversation, often wrapping up with a question or next-step suggestion. | ||||||||||
| Avoid multiple questions in a single response. | ||||||||||
| Get clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity. | ||||||||||
| Use a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am). | ||||||||||
| If you are saying a time like 8:00 AM, just say 8 AM and omit the trailing zeros.`; | ||||||||||
|
|
||||||||||
| const responseGuideline = `## Response Guideline | ||||||||||
| Adapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response. | ||||||||||
| Stay in Character: Keep conversations within your role'''s scope, guiding them back creatively without repeating. | ||||||||||
| Ensure Fluid Dialogue: Respond in a role-appropriate, direct manner to maintain a smooth conversation flow.`; | ||||||||||
|
|
||||||||||
| const scheduleRule = ` ## Schedule Rule | ||||||||||
| Current time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.`; | ||||||||||
|
|
||||||||||
|
Comment on lines
+16
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix stray triple quotes in “that'''s”. User-facing grammar. - Current time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.`;
+ Current time is {{current_time}}. You only schedule time in the current calendar year; you cannot schedule time that's in the past.`;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| // Key are from components/sections/template/data/workflows.ts page in https://github.com/calcom/website | ||||||||||
| export const calAIPhoneWorkflowTemplates = { | ||||||||||
| // name: "Cal AI No-show Follow-up Call", | ||||||||||
| // description: "Automatically call attendee when marked as no-show" | ||||||||||
| "wf-10": { | ||||||||||
| generalPrompt: `## You are calling an attendee who was marked as a no-show for their appointment. Your goal is to help them reschedule. Be understanding, friendly, and non-judgmental. | ||||||||||
|
|
||||||||||
| ${styleGuardrails} | ||||||||||
|
|
||||||||||
| ${responseGuideline} | ||||||||||
|
|
||||||||||
| ${scheduleRule} | ||||||||||
|
|
||||||||||
| ## Task Steps | ||||||||||
| 1. Start with a friendly greeting: "Hi {{ATTENDEE_NAME}}, this is a courtesy call from {{ORGANIZER_NAME}}. I noticed you weren't able to make your {{EVENT_NAME}} appointment on {{EVENT_DATE}} at {{EVENT_TIME}}." | ||||||||||
| 2. Express understanding: "I understand things come up. I'm calling to see if you'd like to reschedule for another time that works better for you." | ||||||||||
| 3. If they want to reschedule: | ||||||||||
| 3a. Ask "When would work best for you to reschedule?" | ||||||||||
| 3b. Call function check_availability_{{eventTypeId}} to check for availability in the user provided time range. | ||||||||||
| - if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot. | ||||||||||
| - if availability does not exist, ask user to select another time range for the appointment, repeat this step 3a. | ||||||||||
| 5. If {{ATTENDEE_EMAIL}} is not unknown then Use name {{ATTENDEE_NAME}} and email {{ATTENDEE_EMAIL}} for creating booking else Ask for user name and email and Confirm the name and email with user by reading it back to user. | ||||||||||
| 6. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment. | ||||||||||
| - if booking returned booking detail, it means booking is successful, proceed to step 7. | ||||||||||
| - if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3a. | ||||||||||
| 7. If they don't want to reschedule: | ||||||||||
| - Thank them for their time and let them know they can always reach out if they change their mind. | ||||||||||
| 8. Before ending, ask if there's anything else you can help with. | ||||||||||
| 9. Thank them for their time and call function end_call to hang up.`, | ||||||||||
| }, | ||||||||||
|
|
||||||||||
| // name: "Cal AI 1-hour Meeting Reminder", | ||||||||||
| // description: "Remind attendee 1 hour before the meeting" | ||||||||||
| "wf-11": { | ||||||||||
| generalPrompt: `## You are calling to remind an attendee about their upcoming appointment in 1 hour. Be friendly, helpful, and concise. | ||||||||||
|
|
||||||||||
| ${styleGuardrails} | ||||||||||
|
|
||||||||||
| ${responseGuideline} | ||||||||||
|
|
||||||||||
| ${scheduleRule} | ||||||||||
|
|
||||||||||
| ## Task Steps | ||||||||||
| 1. Start with a friendly greeting: "Hi {{ATTENDEE_NAME}}, this is a quick reminder call from {{ORGANIZER_NAME}} about your upcoming {{EVENT_NAME}} appointment." | ||||||||||
| 2. Provide the meeting details: "Your appointment is scheduled for today at {{EVENT_TIME}} {{TIMEZONE}}. That's in about an hour." | ||||||||||
| 3. Ask if they'll be able to make it: "Will you be able to join us?" | ||||||||||
| 4. If they confirm attendance: | ||||||||||
| - Thank them and remind them of any preparation needed. | ||||||||||
| - Say "Great! We'll see you at {{EVENT_TIME}}." | ||||||||||
| 5. If they need to reschedule or cancel: | ||||||||||
| - Express understanding: "No problem, these things happen." | ||||||||||
| - Ask: "Would you like to reschedule now, or would you prefer to contact us later?" | ||||||||||
| - If they want to reschedule now: | ||||||||||
| 5a. If {{ATTENDEE_EMAIL}} is not unknown: Use name {{ATTENDEE_NAME}} and email {{ATTENDEE_EMAIL}} for creating booking | ||||||||||
| 5b. If {{ATTENDEE_EMAIL}} is unknown: Ask for user name and email and confirm by reading it back to user | ||||||||||
| 5c. Ask user for "When would you want to reschedule?" | ||||||||||
| 5d. Call function check_availability_{{eventTypeId}} to check for availability in the user provided time range. | ||||||||||
| 5e. If availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot. | ||||||||||
| 5f. If availability does not exist, ask user to select another time range for the appointment (repeat step 5c). | ||||||||||
| 5g. Confirm the date and time selected by user: "Just to confirm, you want to book the appointment at ..." | ||||||||||
| 5h. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment. | ||||||||||
| 5i. If booking returned booking detail, it means booking is successful, proceed to step 7. | ||||||||||
| 5j. If booking returned error message, let user know why the booking was not successful, and maybe start over (return to step 5c). | ||||||||||
| - If they prefer to reschedule later: "No problem. You can reschedule anytime through the link in your confirmation email or by contacting us." | ||||||||||
| 6. If they have questions about the meeting: | ||||||||||
| - Answer based on available information ({{ADDITIONAL_NOTES}}, {{LOCATION}}, etc.). | ||||||||||
| - Common questions to handle: | ||||||||||
| - Duration: Use {{EVENT_END_TIME}} to calculate and state duration | ||||||||||
| - Location details: Provide {{LOCATION}} information | ||||||||||
| - What to prepare: Check {{ADDITIONAL_NOTES}} for any preparation instructions | ||||||||||
| - Who they're meeting: {{ORGANIZER_NAME}} is the person they'll be meeting | ||||||||||
| 7. End with: "Thanks for your time. Have a great day!" and call function end_call to hang up.`, | ||||||||||
| }, | ||||||||||
| }; | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||||||||||||||||||||||||||||
| import type { WorkflowStep } from "@prisma/client"; | ||||||||||||||||||||||||||||||||||||||||
| import { type TFunction } from "i18next"; | ||||||||||||||||||||||||||||||||||||||||
| import { useParams } from "next/navigation"; | ||||||||||||||||||||||||||||||||||||||||
| import { useParams, useRouter, useSearchParams } from "next/navigation"; | ||||||||||||||||||||||||||||||||||||||||
| import type { Dispatch, SetStateAction } from "react"; | ||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useRef, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||
| import type { UseFormReturn } from "react-hook-form"; | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -128,8 +128,10 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { | |||||||||||||||||||||||||||||||||||||||
| const { t, i18n } = useLocale(); | ||||||||||||||||||||||||||||||||||||||||
| const utils = trpc.useUtils(); | ||||||||||||||||||||||||||||||||||||||||
| const params = useParams(); | ||||||||||||||||||||||||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||||||||||||||||||||||||
| const searchParams = useSearchParams(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const { step, form, reload, setReload, teamId } = props; | ||||||||||||||||||||||||||||||||||||||||
| const { step, form, reload, setReload, teamId, onSaveWorkflow } = props; | ||||||||||||||||||||||||||||||||||||||||
| const { data: _verifiedNumbers } = trpc.viewer.workflows.getVerifiedNumbers.useQuery( | ||||||||||||||||||||||||||||||||||||||||
| { teamId }, | ||||||||||||||||||||||||||||||||||||||||
| { enabled: !!teamId } | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -158,7 +160,6 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { | |||||||||||||||||||||||||||||||||||||||
| onSuccess: async (data) => { | ||||||||||||||||||||||||||||||||||||||||
| showToast(t("agent_created_successfully"), "success"); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Update the step's agentId in the form state | ||||||||||||||||||||||||||||||||||||||||
| if (step) { | ||||||||||||||||||||||||||||||||||||||||
| const stepIndex = step.stepNumber - 1; | ||||||||||||||||||||||||||||||||||||||||
| form.setValue(`steps.${stepIndex}.agentId`, data.id); | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -239,6 +240,66 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { | |||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const [timeSectionText, setTimeSectionText] = useState(getTimeSectionText(form.getValues("trigger"), t)); | ||||||||||||||||||||||||||||||||||||||||
| const [autoAgentCreationAttempted, setAutoAgentCreationAttempted] = useState(false); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||
| const autoCreateAgent = searchParams?.get("autoCreateAgent"); | ||||||||||||||||||||||||||||||||||||||||
| const templateWorkflowId = searchParams?.get("templateWorkflowId"); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||
| autoCreateAgent === "true" && | ||||||||||||||||||||||||||||||||||||||||
| !autoAgentCreationAttempted && | ||||||||||||||||||||||||||||||||||||||||
| templateWorkflowId && | ||||||||||||||||||||||||||||||||||||||||
| step && | ||||||||||||||||||||||||||||||||||||||||
| step.action === WorkflowActions.CAL_AI_PHONE_CALL && | ||||||||||||||||||||||||||||||||||||||||
| !stepAgentId && | ||||||||||||||||||||||||||||||||||||||||
| step.id && | ||||||||||||||||||||||||||||||||||||||||
| onSaveWorkflow | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+245
to
+257
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can avoid useEffect here and we should abstract this complex logic out in its own hook(regardless of whether useEffect is avoidable or not) |
||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+249
to
+258
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainRemove step.id from the auto-create guard (blocks intended first-run path) Requiring step.id defeats the purpose of calling onSaveWorkflow to obtain the step ID, so the flow won’t run on brand-new steps. Drop that check. Apply this diff: - step &&
- step.action === WorkflowActions.CAL_AI_PHONE_CALL &&
- !stepAgentId &&
- step.id &&
- onSaveWorkflow
+ step &&
+ step.action === WorkflowActions.CAL_AI_PHONE_CALL &&
+ !stepAgentId &&
+ onSaveWorkflowOptionally also gate on readOnly to avoid unexpected mutations in view-only contexts. 🏁 Script executed: #!/bin/bash
# Find the WorkflowStepContainer.tsx file path
FILE=$(fd WorkflowStepContainer.tsx)
echo "Using file: $FILE"
echo "\n--- Snippet from lines 240-300 ---"
sed -n '240,300p' "$FILE"
echo "\n--- onSaveWorkflow references ---"
rg -n 'onSaveWorkflow' -C2 "$FILE"Length of output: 3030 Remove Requiring - step &&
- step.action === WorkflowActions.CAL_AI_PHONE_CALL &&
- !stepAgentId &&
- step.id &&
- onSaveWorkflow
+ step &&
+ step.action === WorkflowActions.CAL_AI_PHONE_CALL &&
+ !stepAgentId &&
+ onSaveWorkflowConsider also adding 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| setAutoAgentCreationAttempted(true); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const createAgent = async () => { | ||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| await onSaveWorkflow?.(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const updatedSteps = form.getValues("steps"); | ||||||||||||||||||||||||||||||||||||||||
| const currentStepIndex = step.stepNumber - 1; | ||||||||||||||||||||||||||||||||||||||||
| const updatedStep = updatedSteps[currentStepIndex]; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (updatedStep?.id) { | ||||||||||||||||||||||||||||||||||||||||
| createAgentMutation.mutate({ | ||||||||||||||||||||||||||||||||||||||||
| teamId, | ||||||||||||||||||||||||||||||||||||||||
| workflowStepId: updatedStep.id, | ||||||||||||||||||||||||||||||||||||||||
| templateWorkflowId, | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const url = new URL(window.location.href); | ||||||||||||||||||||||||||||||||||||||||
| url.searchParams.delete("autoCreateAgent"); | ||||||||||||||||||||||||||||||||||||||||
| url.searchParams.delete("templateWorkflowId"); | ||||||||||||||||||||||||||||||||||||||||
| router.replace(url.pathname + url.search); | ||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||
| showToast(t("failed_to_get_workflow_step_id"), "error"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to auto-create agent:", error); | ||||||||||||||||||||||||||||||||||||||||
| showToast(t("failed_to_create_agent"), "error"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| createAgent(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }, [ | ||||||||||||||||||||||||||||||||||||||||
| searchParams, | ||||||||||||||||||||||||||||||||||||||||
| autoAgentCreationAttempted, | ||||||||||||||||||||||||||||||||||||||||
| step, | ||||||||||||||||||||||||||||||||||||||||
| stepAgentId, | ||||||||||||||||||||||||||||||||||||||||
| teamId, | ||||||||||||||||||||||||||||||||||||||||
| onSaveWorkflow, | ||||||||||||||||||||||||||||||||||||||||
| createAgentMutation, | ||||||||||||||||||||||||||||||||||||||||
| form, | ||||||||||||||||||||||||||||||||||||||||
| t, | ||||||||||||||||||||||||||||||||||||||||
| router, | ||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); | ||||||||||||||||||||||||||||||||||||||||
| const triggerOptions = getWorkflowTriggerOptions(t); | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -681,8 +742,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { | |||||||||||||||||||||||||||||||||||||||
| color="secondary" | ||||||||||||||||||||||||||||||||||||||||
| onClick={async () => { | ||||||||||||||||||||||||||||||||||||||||
| // save the workflow first to get the step id | ||||||||||||||||||||||||||||||||||||||||
| if (props.onSaveWorkflow) { | ||||||||||||||||||||||||||||||||||||||||
| await props.onSaveWorkflow(); | ||||||||||||||||||||||||||||||||||||||||
| if (onSaveWorkflow) { | ||||||||||||||||||||||||||||||||||||||||
| await onSaveWorkflow(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // After saving, get the updated step ID from the form | ||||||||||||||||||||||||||||||||||||||||
| const updatedSteps = form.getValues("steps"); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||
| import { createDefaultAIPhoneServiceProvider } from "@calcom/features/calAIPhone"; | ||||||
| import type { RetellLLMGeneralTools } from "@calcom/features/calAIPhone/providers/retellAI/types"; | ||||||
| import { calAIPhoneWorkflowTemplates } from "@calcom/features/calAIPhone/workflowTemplates"; | ||||||
|
|
||||||
| import type { TrpcSessionUser } from "../../../types"; | ||||||
| import type { TCreateInputSchema } from "./create.schema"; | ||||||
|
|
@@ -12,16 +13,21 @@ type CreateHandlerOptions = { | |||||
| }; | ||||||
|
|
||||||
| export const createHandler = async ({ ctx, input }: CreateHandlerOptions) => { | ||||||
| const { teamId, name, workflowStepId, ...retellConfig } = input; | ||||||
| const { teamId, name, workflowStepId, templateWorkflowId, ...retellConfig } = input; | ||||||
|
|
||||||
| const aiService = createDefaultAIPhoneServiceProvider(); | ||||||
|
|
||||||
| const generalPrompt = templateWorkflowId | ||||||
| ? calAIPhoneWorkflowTemplates?.[templateWorkflowId as keyof typeof calAIPhoneWorkflowTemplates] | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Don't think calAIPhoneWorkflowTemplates is nullish |
||||||
| ?.generalPrompt | ||||||
| : undefined; | ||||||
|
|
||||||
| return await aiService.createAgent({ | ||||||
| name, | ||||||
| userId: ctx.user.id, | ||||||
| teamId, | ||||||
| workflowStepId, | ||||||
| generalPrompt: retellConfig.generalPrompt, | ||||||
| generalPrompt: generalPrompt ?? retellConfig.generalPrompt, | ||||||
| beginMessage: retellConfig.beginMessage, | ||||||
| generalTools: retellConfig.generalTools as RetellLLMGeneralTools, | ||||||
| userTimeZone: ctx.user.timeZone, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ export const ZCreateInputSchema = z.object({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teamId: z.number().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflowStepId: z.number().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| templateWorkflowId: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Validate templateWorkflowId against known template keys (reject unknown IDs). Right now any string is accepted and silently ignored in the handler. Constrain it to the supported keys to fail fast and avoid confusing fallbacks. Apply: +import { calAIPhoneWorkflowTemplates } from "@calcom/features/calAIPhone/workflowTemplates";
export const ZCreateInputSchema = z.object({
name: z.string().optional(),
- teamId: z.number().optional(),
- workflowStepId: z.number().optional(),
- templateWorkflowId: z.string().optional(),
+ teamId: z.number().int().positive().optional(),
+ workflowStepId: z.number().int().positive().optional(),
+ templateWorkflowId: z
+ .string()
+ .optional()
+ .refine(
+ (v) => !v || v in calAIPhoneWorkflowTemplates,
+ "Invalid templateWorkflowId"
+ ),
generalPrompt: z.string().optional(),
beginMessage: z.string().optional(),
...
});
+// Optionally enforce mutual exclusivity to avoid ambiguity
+export const ZCreateInputSchemaExclusive = ZCreateInputSchema.refine(
+ (d) => !(d.templateWorkflowId && d.generalPrompt),
+ { message: "Provide either templateWorkflowId or generalPrompt, not both.", path: ["templateWorkflowId"] }
+);📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| generalPrompt: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beginMessage: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| generalTools: z | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.