import * as z from "zod" import { defineAction } from "../../../automation/actions" import { findAvailableSlots, getGoogleCalendarApi, normalizeFreeBusy, resolveCalendarIds, } from "@automate.ax/integration-contracts/google-calendar" import { GOOGLE_CALENDAR_REFERENCE_FREE_BUSY_REQUIREMENT } from "../lib/scopes" import { AVAILABILITY_SLOT_SCHEMA, CALENDAR_REFERENCE_SCHEMA, FREE_BUSY_SCHEMA, RFC3339_SCHEMA, } from "@automate.ax/integration-contracts/google-calendar" const FREE_BUSY_INPUT_SCHEMA = z .object({ /** Exact calendar titles or IDs, or Google Group IDs, to query. */ calendars: CALENDAR_REFERENCE_SCHEMA.array().min(1).max(50), /** Exclusive RFC 3339 query end. */ timeMax: RFC3339_SCHEMA, /** Inclusive RFC 3339 query start. */ timeMin: RFC3339_SCHEMA, /** IANA response timezone. */ timeZone: z.string().min(1).optional(), }) .refine( ({ timeMax, timeMin }) => new Date(timeMax).getTime() > new Date(timeMin).getTime(), "The free/busy query end must follow its start.", ) /** Queries busy periods across calendars and Google Groups. */ export const queryGoogleCalendarFreeBusy = defineAction( "Query Google Calendar free/busy", ) .describe("Returns busy periods across up to 50 calendars or groups.") .account("google", GOOGLE_CALENDAR_REFERENCE_FREE_BUSY_REQUIREMENT) .input(FREE_BUSY_INPUT_SCHEMA) .output(FREE_BUSY_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const calendarApi = getGoogleCalendarApi(account.secret) const { data } = await calendarApi.freebusy.query({ requestBody: { calendarExpansionMax: 50, groupExpansionMax: 100, items: (await resolveCalendarIds(calendarApi, input.calendars)).map( (id) => ({ id }), ), timeMax: input.timeMax, timeMin: input.timeMin, timeZone: input.timeZone, }, }) return normalizeFreeBusy(data) }) /** Finds common available slots across calendars. */ export const findGoogleCalendarAvailability = defineAction( "Find Google Calendar availability", ) .describe("Finds common free slots across multiple calendars.") .account("google", GOOGLE_CALENDAR_REFERENCE_FREE_BUSY_REQUIREMENT) .input( FREE_BUSY_INPUT_SCHEMA.extend({ /** Required slot duration in minutes. */ durationMinutes: z .number() .int() .min(1) .max(24 * 60), /** Maximum number of candidate slots returned. */ limit: z.number().int().min(1).max(1000).optional(), /** Candidate start-time increment in minutes. */ stepMinutes: z .number() .int() .min(1) .max(24 * 60) .optional(), }), ) .output(AVAILABILITY_SLOT_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const calendarApi = getGoogleCalendarApi(account.secret) const { data } = await calendarApi.freebusy.query({ requestBody: { calendarExpansionMax: 50, groupExpansionMax: 100, items: (await resolveCalendarIds(calendarApi, input.calendars)).map( (id) => ({ id }), ), timeMax: input.timeMax, timeMin: input.timeMin, timeZone: input.timeZone, }, }) return findAvailableSlots( normalizeFreeBusy(data), input.durationMinutes, input.stepMinutes ?? 15, input.limit ?? 20, ) })