import { createLogger } from '@sim/logger' import { CALENDAR_API_BASE, type GoogleCalendarApiEventResponse, type GoogleCalendarQuickAddParams, type GoogleCalendarQuickAddResponse, } from '@/tools/google_calendar/types' import type { ToolConfig } from '@/tools/types' const logger = createLogger('GoogleCalendarQuickAddTool') export const quickAddTool: ToolConfig< GoogleCalendarQuickAddParams, GoogleCalendarQuickAddResponse > = { id: 'google_calendar_quick_add', name: 'Google Calendar Quick Add', description: 'Create events from natural language text', version: '1.0.0', oauth: { required: true, provider: 'google-calendar', }, params: { accessToken: { type: 'string', required: true, visibility: 'hidden', description: 'Access token for Google Calendar API', }, calendarId: { type: 'string', required: false, visibility: 'user-or-llm', description: 'Google Calendar ID (e.g., primary or calendar@group.calendar.google.com)', }, text: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Natural language text describing the event (e.g., "Meeting with John tomorrow at 3pm")', }, attendees: { type: 'array', required: false, visibility: 'user-or-llm', description: 'Array of attendee email addresses (comma-separated string also accepted)', }, sendUpdates: { type: 'string', required: false, visibility: 'user-only', description: 'How to send updates to attendees: all, externalOnly, or none', }, }, request: { url: (params: GoogleCalendarQuickAddParams) => { const calendarId = params.calendarId || 'primary' const queryParams = new URLSearchParams() queryParams.append('text', params.text) if (params.sendUpdates !== undefined) { queryParams.append('sendUpdates', params.sendUpdates) } return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/quickAdd?${queryParams.toString()}` }, method: 'POST', headers: (params: GoogleCalendarQuickAddParams) => ({ Authorization: `Bearer ${params.accessToken}`, 'Content-Type': 'application/json', }), }, transformResponse: async (response: Response, params) => { const data: GoogleCalendarApiEventResponse = await response.json() let finalEventData: GoogleCalendarApiEventResponse = data if (params?.attendees) { let attendeeList: string[] = [] const attendees = params.attendees as string | string[] if (Array.isArray(attendees)) { attendeeList = attendees.filter((email: string) => email && email.trim().length > 0) } else if (typeof attendees === 'string' && attendees.trim().length > 0) { attendeeList = attendees .split(',') .map((email: string) => email.trim()) .filter((email: string) => email.length > 0) } if (attendeeList.length > 0) { try { const calendarId = params.calendarId || 'primary' const eventId = data.id const updateData = { attendees: attendeeList.map((email: string) => ({ email })), } const updateQueryParams = new URLSearchParams() if (params.sendUpdates !== undefined) { updateQueryParams.append('sendUpdates', params.sendUpdates) } const updateUrl = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${eventId}${updateQueryParams.toString() ? `?${updateQueryParams.toString()}` : ''}` const updateResponse = await fetch(updateUrl, { method: 'PATCH', headers: { Authorization: `Bearer ${params.accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(updateData), }) if (updateResponse.ok) { finalEventData = await updateResponse.json() } else { logger.warn('Failed to add attendees to quick-added event', { error: await updateResponse.text(), }) } } catch (error) { logger.warn('Error adding attendees to quick-added event', { error }) } } } return { success: true, output: { content: `Event "${finalEventData?.summary || 'Untitled'}" created successfully ${finalEventData?.attendees?.length ? ` with ${finalEventData.attendees.length} attendee(s)` : ''}`, metadata: { id: finalEventData.id, htmlLink: finalEventData.htmlLink, status: finalEventData.status, summary: finalEventData.summary, description: finalEventData.description, location: finalEventData.location, start: finalEventData.start, end: finalEventData.end, attendees: finalEventData.attendees, creator: finalEventData.creator, organizer: finalEventData.organizer, }, }, } }, outputs: { content: { type: 'string', description: 'Event creation confirmation message from natural language', }, metadata: { type: 'json', description: 'Created event metadata including parsed details', properties: { id: { type: 'string', description: 'Event ID' }, htmlLink: { type: 'string', description: 'URL to view the event in Google Calendar' }, status: { type: 'string', description: 'Event status (confirmed, tentative, cancelled)' }, summary: { type: 'string', description: 'Event title' }, description: { type: 'string', description: 'Event description' }, location: { type: 'string', description: 'Event location' }, start: { type: 'object', description: 'Event start time' }, end: { type: 'object', description: 'Event end time' }, attendees: { type: 'array', description: 'List of event attendees' }, creator: { type: 'object', description: 'Event creator info' }, organizer: { type: 'object', description: 'Event organizer info' }, }, }, }, } interface GoogleCalendarQuickAddV2Response { success: boolean output: { id: string htmlLink: string status: string summary: string | null description: string | null location: string | null start: GoogleCalendarApiEventResponse['start'] end: GoogleCalendarApiEventResponse['end'] attendees: GoogleCalendarApiEventResponse['attendees'] | null creator: GoogleCalendarApiEventResponse['creator'] organizer: GoogleCalendarApiEventResponse['organizer'] } } export const quickAddV2Tool: ToolConfig< GoogleCalendarQuickAddParams, GoogleCalendarQuickAddV2Response > = { id: 'google_calendar_quick_add_v2', name: 'Google Calendar Quick Add', description: 'Create events from natural language text. Returns API-aligned fields only.', version: '2.0.0', oauth: quickAddTool.oauth, params: quickAddTool.params, request: quickAddTool.request, transformResponse: async (response: Response, params) => { const data: GoogleCalendarApiEventResponse = await response.json() let finalEventData: GoogleCalendarApiEventResponse = data if (params?.attendees) { let attendeeList: string[] = [] const attendees = params.attendees as string | string[] if (Array.isArray(attendees)) { attendeeList = attendees.filter((email: string) => email && email.trim().length > 0) } else if (typeof attendees === 'string' && attendees.trim().length > 0) { attendeeList = attendees .split(',') .map((email: string) => email.trim()) .filter((email: string) => email.length > 0) } if (attendeeList.length > 0) { try { const calendarId = params.calendarId || 'primary' const eventId = data.id const updateData = { attendees: attendeeList.map((email: string) => ({ email })), } const updateQueryParams = new URLSearchParams() if (params.sendUpdates !== undefined) { updateQueryParams.append('sendUpdates', params.sendUpdates) } const updateUrl = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${eventId}${updateQueryParams.toString() ? `?${updateQueryParams.toString()}` : ''}` const updateResponse = await fetch(updateUrl, { method: 'PATCH', headers: { Authorization: `Bearer ${params.accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(updateData), }) if (updateResponse.ok) { finalEventData = await updateResponse.json() } else { logger.warn('Failed to add attendees to quick-added event', { error: await updateResponse.text(), }) } } catch (error) { logger.warn('Error adding attendees to quick-added event', { error }) } } } return { success: true, output: { id: finalEventData.id, htmlLink: finalEventData.htmlLink, status: finalEventData.status, summary: finalEventData.summary ?? null, description: finalEventData.description ?? null, location: finalEventData.location ?? null, start: finalEventData.start, end: finalEventData.end, attendees: finalEventData.attendees ?? null, creator: finalEventData.creator, organizer: finalEventData.organizer, }, } }, outputs: { id: { type: 'string', description: 'Event ID' }, htmlLink: { type: 'string', description: 'Event link' }, status: { type: 'string', description: 'Event status' }, summary: { type: 'string', description: 'Event title', optional: true }, description: { type: 'string', description: 'Event description', optional: true }, location: { type: 'string', description: 'Event location', optional: true }, start: { type: 'json', description: 'Event start' }, end: { type: 'json', description: 'Event end' }, attendees: { type: 'json', description: 'Event attendees', optional: true }, creator: { type: 'json', description: 'Event creator' }, organizer: { type: 'json', description: 'Event organizer' }, }, }