feat(calendar): introduce eventType (Out of Office, Focus Time, Working Location) in Calendar Service#290
Conversation
…ng Location) in Calendar Service
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly expands the Calendar Service's capabilities by integrating support for Google Calendar's status events. It introduces dedicated functionalities for managing 'Focus Time', 'Out of Office', and 'Working Location' events, allowing users to programmatically set their availability and location with rich details. Furthermore, it enhances the event retrieval mechanism to recognize and filter these new event types, providing a more comprehensive view of a user's calendar status. This change improves the utility of the calendar service for automated scheduling and status management. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces several new features for Google Calendar, including tools to create focus time, out-of-office, and working location events, and enhances the listEvents tool with eventType filtering. The changes are well-implemented and include comprehensive tests. My feedback focuses on improving input validation for the new tools, reducing code duplication, and simplifying some of the implementation logic for better maintainability.
| inputSchema: { | ||
| calendarId: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| 'The ID of the calendar. Defaults to the primary calendar.', | ||
| ), | ||
| summary: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| 'The title of the working location event. Defaults to "Working Location".', | ||
| ), | ||
| start: z.object({ | ||
| dateTime: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| 'The start time in strict ISO 8601 format. Use for timed events.', | ||
| ), | ||
| date: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| 'The start date in YYYY-MM-DD format. Use for all-day events.', | ||
| ), | ||
| }), | ||
| end: z.object({ | ||
| dateTime: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| 'The end time in strict ISO 8601 format. Use for timed events.', | ||
| ), | ||
| date: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| 'The end date in YYYY-MM-DD format. Use for all-day events (exclusive, so use next day).', | ||
| ), | ||
| }), | ||
| type: z | ||
| .enum(['homeOffice', 'officeLocation', 'customLocation']) | ||
| .describe( | ||
| 'The type of working location.', | ||
| ), | ||
| officeLocation: z | ||
| .object({ | ||
| buildingId: z | ||
| .string() | ||
| .optional() | ||
| .describe('The building ID from the directory.'), | ||
| label: z | ||
| .string() | ||
| .optional() | ||
| .describe('Label for the office location.'), | ||
| }) | ||
| .optional() | ||
| .describe( | ||
| 'Office location details. Required when type is "officeLocation".', | ||
| ), | ||
| customLocation: z | ||
| .object({ | ||
| label: z | ||
| .string() | ||
| .describe('Label for the custom location.'), | ||
| }) | ||
| .optional() | ||
| .describe( | ||
| 'Custom location details. Required when type is "customLocation".', | ||
| ), | ||
| }, |
There was a problem hiding this comment.
The inputSchema for calendar.setWorkingLocation could be made more robust to provide better feedback for invalid inputs. Currently, it allows for ambiguous or incomplete data to be passed.
Specifically:
- The
startandendobjects should require at least one ofdateordateTimeto be provided. officeLocationshould be required whentypeisofficeLocation.customLocationshould be required whentypeiscustomLocation.
You can enforce these rules using zod's .superRefine() method on the object schema. This will provide clearer error messages to the user at the validation stage, rather than relying on a downstream API error.
Example of refinement:
.superRefine((data, ctx) => {
if (!data.start.date && !data.start.dateTime) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Either start.date or start.dateTime must be provided.',
path: ['start'],
});
}
if (data.type === 'officeLocation' && !data.officeLocation) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'officeLocation is required when type is "officeLocation".',
path: ['officeLocation'],
});
}
// ... and so on for other conditions
})| createFocusTime = async (input: CreateFocusTimeInput) => { | ||
| const { | ||
| calendarId, | ||
| summary = 'Focus Time', | ||
| start, | ||
| end, | ||
| chatStatus = 'doNotDisturb', | ||
| autoDeclineMode = 'declineOnlyNewConflictingInvitations', | ||
| declineMessage, | ||
| } = input; | ||
|
|
||
| try { | ||
| iso8601DateTimeSchema.parse(start.dateTime); | ||
| iso8601DateTimeSchema.parse(end.dateTime); | ||
| } catch (error) { | ||
| return this.createValidationErrorResponse(error); | ||
| } | ||
|
|
||
| const finalCalendarId = calendarId || (await this.getPrimaryCalendarId()); | ||
| logToFile(`Creating focus time in calendar: ${finalCalendarId}`); | ||
|
|
||
| try { | ||
| const calendar = await this.getCalendar(); | ||
| const event: calendar_v3.Schema$Event = { | ||
| summary, | ||
| start, | ||
| end, | ||
| eventType: 'focusTime', | ||
| transparency: 'opaque', | ||
| focusTimeProperties: { | ||
| chatStatus, | ||
| autoDeclineMode, | ||
| declineMessage, | ||
| }, | ||
| }; | ||
|
|
||
| const res = await calendar.events.insert({ | ||
| calendarId: finalCalendarId, | ||
| requestBody: event, | ||
| }); | ||
| logToFile(`Successfully created focus time: ${res.data.id}`); | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text' as const, | ||
| text: JSON.stringify(res.data), | ||
| }, | ||
| ], | ||
| }; | ||
| } catch (error) { | ||
| const errorMessage = | ||
| error instanceof Error ? error.message : String(error); | ||
| logToFile(`Error during calendar.createFocusTime: ${errorMessage}`); | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text' as const, | ||
| text: JSON.stringify({ error: errorMessage }), | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| }; |
There was a problem hiding this comment.
The three new methods createFocusTime, createOutOfOffice, and setWorkingLocation share a significant amount of boilerplate code for handling API responses, both for success and error cases. To improve maintainability and reduce code duplication, consider extracting this common logic into private helper methods within the CalendarService class.
For example, you could create methods like createApiSuccessResponse(data, logMessage) and handleApiError(error, toolName) to encapsulate the response formatting and error logging.
| } else if (type === 'officeLocation' && officeLocation) { | ||
| workingLocationProperties.officeLocation = { | ||
| buildingId: officeLocation.buildingId, | ||
| label: officeLocation.label, | ||
| }; | ||
| } else if (type === 'customLocation' && customLocation) { | ||
| workingLocationProperties.customLocation = { | ||
| label: customLocation.label, | ||
| }; | ||
| } |
There was a problem hiding this comment.
The construction of workingLocationProperties can be simplified. Since the officeLocation and customLocation input objects are compatible with the types expected by the Google Calendar API, you can assign them directly instead of manually reconstructing them. This makes the code more concise and easier to maintain if more properties are added in the future.
} else if (type === 'officeLocation' && officeLocation) {
workingLocationProperties.officeLocation = officeLocation;
} else if (type === 'customLocation' && customLocation) {
workingLocationProperties.customLocation = customLocation;
}|
Hey @galuszkak — the However, I'd like to rethink the approach of adding three separate tools ( Why:
What I'd like to see instead: Add optional fields to
Then have Additionally, this is a good opportunity to fix #177 in the same PR — To summarize the ask:
|
Implements Google Calendar Status Events by adding three new MCP tools -
calendar.createFocusTime,calendar.createOutOfOffice, andcalendar.setWorkingLocation- that allow users to create status events with their respective properties such as auto-decline mode, decline messages, chat status, and working location details. Thecalendar.listEventstool is extended with an eventTypes filter parameter so users can query specific event types likefocusTime,outOfOffice,workingLocation, birthday, or fromGmail, and its response fields now include eventType, focusTimeProperties, outOfOfficeProperties, and workingLocationProperties. This change also fixes an issue where status events without a summary were silently dropped from list results, since these event types may not always have a summary set.