import { z } from 'zod' import fetch from 'node-fetch' import { DynamicStructuredTool } from '../OpenAPIToolkit/core' import { TOOL_ARGS_PREFIX, formatToolError } from '../../../src/agents' export const desc = `Use this when you want to access Google Calendar API for managing events and calendars` export interface Headers { [key: string]: string } export interface Body { [key: string]: any } export interface RequestParameters { headers?: Headers body?: Body url?: string description?: string name?: string actions?: string[] accessToken?: string defaultParams?: any } // Define schemas for different Google Calendar operations // Event Schemas const ListEventsSchema = z.object({ calendarId: z.string().default('primary').describe('Calendar ID (use "primary" for primary calendar)'), timeMin: z.string().optional().describe('Lower bound for event search (RFC3339 timestamp)'), timeMax: z.string().optional().describe('Upper bound for event search (RFC3339 timestamp)'), maxResults: z.number().optional().default(250).describe('Maximum number of events to return'), singleEvents: z.boolean().optional().default(true).describe('Whether to expand recurring events into instances'), orderBy: z.enum(['startTime', 'updated']).optional().describe('Order of events returned'), query: z.string().optional().describe('Free text search terms') }) const CreateEventSchema = z.object({ calendarId: z.string().default('primary').describe('Calendar ID where the event will be created'), summary: z.string().describe('Event title/summary'), description: z.string().optional().describe('Event description'), location: z.string().optional().describe('Event location'), startDateTime: z.string().optional().describe('Event start time (ISO 8601 format)'), endDateTime: z.string().optional().describe('Event end time (ISO 8601 format)'), startDate: z.string().optional().describe('Start date for all-day events (YYYY-MM-DD)'), endDate: z.string().optional().describe('End date for all-day events (YYYY-MM-DD)'), timeZone: z.string().optional().describe('Time zone (e.g., America/New_York)'), attendees: z.string().optional().describe('Comma-separated list of attendee emails'), recurrence: z.string().optional().describe('Recurrence rules (RRULE format)'), reminderMinutes: z.number().optional().describe('Minutes before event to send reminder'), visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Event visibility') }) const GetEventSchema = z.object({ calendarId: z.string().default('primary').describe('Calendar ID'), eventId: z.string().describe('Event ID') }) const UpdateEventSchema = z.object({ calendarId: z.string().default('primary').describe('Calendar ID'), eventId: z.string().describe('Event ID'), summary: z.string().optional().describe('Updated event title/summary'), description: z.string().optional().describe('Updated event description'), location: z.string().optional().describe('Updated event location'), startDateTime: z.string().optional().describe('Updated event start time (ISO 8601 format)'), endDateTime: z.string().optional().describe('Updated event end time (ISO 8601 format)'), startDate: z.string().optional().describe('Updated start date for all-day events (YYYY-MM-DD)'), endDate: z.string().optional().describe('Updated end date for all-day events (YYYY-MM-DD)'), timeZone: z.string().optional().describe('Updated time zone'), attendees: z.string().optional().describe('Updated comma-separated list of attendee emails'), recurrence: z.string().optional().describe('Updated recurrence rules'), reminderMinutes: z.number().optional().describe('Updated reminder minutes'), visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Updated event visibility') }) const DeleteEventSchema = z.object({ calendarId: z.string().default('primary').describe('Calendar ID'), eventId: z.string().describe('Event ID to delete') }) const QuickAddEventSchema = z.object({ calendarId: z.string().default('primary').describe('Calendar ID'), quickAddText: z.string().describe('Natural language text for quick event creation') }) // Calendar Schemas const ListCalendarsSchema = z.object({ showHidden: z.boolean().optional().describe('Whether to show hidden calendars'), minAccessRole: z.enum(['freeBusyReader', 'reader', 'writer', 'owner']).optional().describe('Minimum access role') }) const CreateCalendarSchema = z.object({ summary: z.string().describe('Calendar title/name'), description: z.string().optional().describe('Calendar description'), location: z.string().optional().describe('Calendar location'), timeZone: z.string().optional().describe('Calendar time zone (e.g., America/New_York)') }) const GetCalendarSchema = z.object({ calendarId: z.string().describe('Calendar ID') }) const UpdateCalendarSchema = z.object({ calendarId: z.string().describe('Calendar ID'), summary: z.string().optional().describe('Updated calendar title/name'), description: z.string().optional().describe('Updated calendar description'), location: z.string().optional().describe('Updated calendar location'), timeZone: z.string().optional().describe('Updated calendar time zone') }) const DeleteCalendarSchema = z.object({ calendarId: z.string().describe('Calendar ID to delete') }) const ClearCalendarSchema = z.object({ calendarId: z.string().describe('Calendar ID to clear (removes all events)') }) // Freebusy Schemas const QueryFreebusySchema = z.object({ timeMin: z.string().describe('Lower bound for freebusy query (RFC3339 timestamp)'), timeMax: z.string().describe('Upper bound for freebusy query (RFC3339 timestamp)'), calendarIds: z.string().describe('Comma-separated list of calendar IDs to check for free/busy info'), groupExpansionMax: z.number().optional().describe('Maximum number of calendars for which FreeBusy information is to be provided'), calendarExpansionMax: z.number().optional().describe('Maximum number of events that can be expanded for each calendar') }) class BaseGoogleCalendarTool extends DynamicStructuredTool { protected accessToken: string = '' constructor(args: any) { super(args) this.accessToken = args.accessToken ?? '' } async makeGoogleCalendarRequest({ endpoint, method = 'GET', body, params }: { endpoint: string method?: string body?: any params?: any }): Promise { const url = `https://www.googleapis.com/calendar/v3/${endpoint}` const headers = { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', Accept: 'application/json', ...this.headers } const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined }) if (!response.ok) { const errorText = await response.text() throw new Error(`Google Calendar API Error ${response.status}: ${response.statusText} - ${errorText}`) } const data = await response.text() return data + TOOL_ARGS_PREFIX + JSON.stringify(params) } } // Event Tools class ListEventsTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'list_events', description: 'List events from Google Calendar', schema: ListEventsSchema, baseUrl: '', method: 'GET', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } const queryParams = new URLSearchParams() if (params.timeMin) queryParams.append('timeMin', params.timeMin) if (params.timeMax) queryParams.append('timeMax', params.timeMax) if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString()) if (params.singleEvents !== undefined) queryParams.append('singleEvents', params.singleEvents.toString()) if (params.orderBy) queryParams.append('orderBy', params.orderBy) if (params.query) queryParams.append('q', params.query) const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events?${queryParams.toString()}` try { const response = await this.makeGoogleCalendarRequest({ endpoint, params }) return response } catch (error) { return formatToolError(`Error listing events: ${error}`, params) } } } class CreateEventTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'create_event', description: 'Create a new event in Google Calendar', schema: CreateEventSchema, baseUrl: '', method: 'POST', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const eventData: any = { summary: params.summary } if (params.description) eventData.description = params.description if (params.location) eventData.location = params.location // Handle date/time if (params.startDate && params.endDate) { // All-day event eventData.start = { date: params.startDate } eventData.end = { date: params.endDate } } else if (params.startDateTime && params.endDateTime) { // Timed event eventData.start = { dateTime: params.startDateTime, timeZone: params.timeZone || 'UTC' } eventData.end = { dateTime: params.endDateTime, timeZone: params.timeZone || 'UTC' } } // Handle attendees if (params.attendees) { eventData.attendees = params.attendees.split(',').map((email: string) => ({ email: email.trim() })) } // Handle recurrence if (params.recurrence) { eventData.recurrence = [params.recurrence] } // Handle reminders if (params.reminderMinutes !== undefined) { eventData.reminders = { useDefault: false, overrides: [ { method: 'popup', minutes: params.reminderMinutes } ] } } if (params.visibility) eventData.visibility = params.visibility const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: eventData, params }) return response } catch (error) { return formatToolError(`Error creating event: ${error}`, params) } } } class GetEventTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'get_event', description: 'Get a specific event from Google Calendar', schema: GetEventSchema, baseUrl: '', method: 'GET', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}` const response = await this.makeGoogleCalendarRequest({ endpoint, params }) return response } catch (error) { return formatToolError(`Error getting event: ${error}`, params) } } } class UpdateEventTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'update_event', description: 'Update an existing event in Google Calendar', schema: UpdateEventSchema, baseUrl: '', method: 'PUT', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const updateData: any = {} if (params.summary) updateData.summary = params.summary if (params.description) updateData.description = params.description if (params.location) updateData.location = params.location // Handle date/time updates if (params.startDate && params.endDate) { updateData.start = { date: params.startDate } updateData.end = { date: params.endDate } } else if (params.startDateTime && params.endDateTime) { updateData.start = { dateTime: params.startDateTime, timeZone: params.timeZone || 'UTC' } updateData.end = { dateTime: params.endDateTime, timeZone: params.timeZone || 'UTC' } } if (params.attendees) { updateData.attendees = params.attendees.split(',').map((email: string) => ({ email: email.trim() })) } if (params.recurrence) { updateData.recurrence = [params.recurrence] } if (params.reminderMinutes !== undefined) { updateData.reminders = { useDefault: false, overrides: [ { method: 'popup', minutes: params.reminderMinutes } ] } } if (params.visibility) updateData.visibility = params.visibility const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'PUT', body: updateData, params }) return response } catch (error) { return formatToolError(`Error updating event: ${error}`, params) } } } class DeleteEventTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'delete_event', description: 'Delete an event from Google Calendar', schema: DeleteEventSchema, baseUrl: '', method: 'DELETE', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'DELETE', params }) return response || 'Event deleted successfully' } catch (error) { return formatToolError(`Error deleting event: ${error}`, params) } } } class QuickAddEventTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'quick_add_event', description: 'Quick add event to Google Calendar using natural language', schema: QuickAddEventSchema, baseUrl: '', method: 'POST', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const queryParams = new URLSearchParams() queryParams.append('text', params.quickAddText) const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/quickAdd?${queryParams.toString()}` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', params }) return response } catch (error) { return formatToolError(`Error quick adding event: ${error}`, params) } } } // Calendar Tools class ListCalendarsTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'list_calendars', description: 'List calendars from Google Calendar', schema: ListCalendarsSchema, baseUrl: '', method: 'GET', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } const queryParams = new URLSearchParams() if (params.showHidden !== undefined) queryParams.append('showHidden', params.showHidden.toString()) if (params.minAccessRole) queryParams.append('minAccessRole', params.minAccessRole) const endpoint = `users/me/calendarList?${queryParams.toString()}` try { const response = await this.makeGoogleCalendarRequest({ endpoint, params }) return response } catch (error) { return formatToolError(`Error listing calendars: ${error}`, params) } } } class CreateCalendarTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'create_calendar', description: 'Create a new calendar in Google Calendar', schema: CreateCalendarSchema, baseUrl: '', method: 'POST', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const calendarData: any = { summary: params.summary } if (params.description) calendarData.description = params.description if (params.location) calendarData.location = params.location if (params.timeZone) calendarData.timeZone = params.timeZone const endpoint = 'calendars' const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: calendarData, params }) return response } catch (error) { return formatToolError(`Error creating calendar: ${error}`, params) } } } class GetCalendarTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'get_calendar', description: 'Get a specific calendar from Google Calendar', schema: GetCalendarSchema, baseUrl: '', method: 'GET', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const endpoint = `calendars/${encodeURIComponent(params.calendarId)}` const response = await this.makeGoogleCalendarRequest({ endpoint, params }) return response } catch (error) { return formatToolError(`Error getting calendar: ${error}`, params) } } } class UpdateCalendarTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'update_calendar', description: 'Update an existing calendar in Google Calendar', schema: UpdateCalendarSchema, baseUrl: '', method: 'PUT', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const updateData: any = {} if (params.summary) updateData.summary = params.summary if (params.description) updateData.description = params.description if (params.location) updateData.location = params.location if (params.timeZone) updateData.timeZone = params.timeZone const endpoint = `calendars/${encodeURIComponent(params.calendarId)}` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'PUT', body: updateData, params }) return response } catch (error) { return formatToolError(`Error updating calendar: ${error}`, params) } } } class DeleteCalendarTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'delete_calendar', description: 'Delete a calendar from Google Calendar', schema: DeleteCalendarSchema, baseUrl: '', method: 'DELETE', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const endpoint = `calendars/${encodeURIComponent(params.calendarId)}` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'DELETE', params }) return response || 'Calendar deleted successfully' } catch (error) { return formatToolError(`Error deleting calendar: ${error}`, params) } } } class ClearCalendarTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'clear_calendar', description: 'Clear all events from a Google Calendar', schema: ClearCalendarSchema, baseUrl: '', method: 'POST', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/clear` const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', params }) return response || 'Calendar cleared successfully' } catch (error) { return formatToolError(`Error clearing calendar: ${error}`, params) } } } // Freebusy Tools class QueryFreebusyTool extends BaseGoogleCalendarTool { defaultParams: any constructor(args: any) { const toolInput = { name: 'query_freebusy', description: 'Query free/busy information for a set of calendars', schema: QueryFreebusySchema, baseUrl: '', method: 'POST', headers: {} } super({ ...toolInput, accessToken: args.accessToken }) this.defaultParams = args.defaultParams || {} } async _call(arg: any): Promise { const params = { ...arg, ...this.defaultParams } try { const freebusyData: any = { timeMin: params.timeMin, timeMax: params.timeMax, items: params.calendarIds.split(',').map((id: string) => ({ id: id.trim() })) } if (params.groupExpansionMax !== undefined) { freebusyData.groupExpansionMax = params.groupExpansionMax } if (params.calendarExpansionMax !== undefined) { freebusyData.calendarExpansionMax = params.calendarExpansionMax } const endpoint = 'freeBusy' const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: freebusyData, params }) return response } catch (error) { return formatToolError(`Error querying freebusy: ${error}`, params) } } } export const createGoogleCalendarTools = (args?: RequestParameters): DynamicStructuredTool[] => { const tools: DynamicStructuredTool[] = [] const actions = args?.actions || [] const accessToken = args?.accessToken || '' const defaultParams = args?.defaultParams || {} // Event tools if (actions.includes('listEvents')) { tools.push(new ListEventsTool({ accessToken, defaultParams })) } if (actions.includes('createEvent')) { tools.push(new CreateEventTool({ accessToken, defaultParams })) } if (actions.includes('getEvent')) { tools.push(new GetEventTool({ accessToken, defaultParams })) } if (actions.includes('updateEvent')) { tools.push(new UpdateEventTool({ accessToken, defaultParams })) } if (actions.includes('deleteEvent')) { tools.push(new DeleteEventTool({ accessToken, defaultParams })) } if (actions.includes('quickAddEvent')) { tools.push(new QuickAddEventTool({ accessToken, defaultParams })) } // Calendar tools if (actions.includes('listCalendars')) { tools.push(new ListCalendarsTool({ accessToken, defaultParams })) } if (actions.includes('createCalendar')) { tools.push(new CreateCalendarTool({ accessToken, defaultParams })) } if (actions.includes('getCalendar')) { tools.push(new GetCalendarTool({ accessToken, defaultParams })) } if (actions.includes('updateCalendar')) { tools.push(new UpdateCalendarTool({ accessToken, defaultParams })) } if (actions.includes('deleteCalendar')) { tools.push(new DeleteCalendarTool({ accessToken, defaultParams })) } if (actions.includes('clearCalendar')) { tools.push(new ClearCalendarTool({ accessToken, defaultParams })) } // Freebusy tools if (actions.includes('queryFreebusy')) { tools.push(new QueryFreebusyTool({ accessToken, defaultParams })) } return tools }