Skip to content

feat(calendar): introduce eventType (Out of Office, Focus Time, Working Location) in Calendar Service#290

Open
galuszkak wants to merge 1 commit intogemini-cli-extensions:mainfrom
galuszkak:feat/eventTypes
Open

feat(calendar): introduce eventType (Out of Office, Focus Time, Working Location) in Calendar Service#290
galuszkak wants to merge 1 commit intogemini-cli-extensions:mainfrom
galuszkak:feat/eventTypes

Conversation

@galuszkak
Copy link

@galuszkak galuszkak commented Mar 24, 2026

Implements Google Calendar Status Events by adding three new MCP tools - calendar.createFocusTime, calendar.createOutOfOffice, and calendar.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. The calendar.listEvents tool is extended with an eventTypes filter parameter so users can query specific event types like focusTime, 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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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 Calendar Tools Introduced: Three new tools have been added to the Calendar Service: calendar.createFocusTime, calendar.createOutOfOffice, and calendar.setWorkingLocation. These tools allow users to create specific status events with configurable properties like auto-decline modes, chat status, and working location details.
  • Enhanced Event Listing: The calendar.listEvents tool has been extended to include an eventTypes filter parameter, enabling users to query specific event types such as focusTime, outOfOffice, workingLocation, birthday, or fromGmail. The response fields now also include eventType, focusTimeProperties, outOfOfficeProperties, and workingLocationProperties.
  • Fix for Summary-less Status Events: A bug has been fixed where status events without a summary were silently dropped from listEvents results. The updated logic ensures that events with a defined eventType (other than 'default') are now correctly included, even if they lack a summary.

🧠 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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +939 to +1010
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".',
),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 start and end objects should require at least one of date or dateTime to be provided.
  • officeLocation should be required when type is officeLocation.
  • customLocation should be required when type is customLocation.

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
})

Comment on lines +864 to +926
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 }),
},
],
};
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +1024 to +1033
} else if (type === 'officeLocation' && officeLocation) {
workingLocationProperties.officeLocation = {
buildingId: officeLocation.buildingId,
label: officeLocation.label,
};
} else if (type === 'customLocation' && customLocation) {
workingLocationProperties.customLocation = {
label: customLocation.label,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
      }

@allenhutchison
Copy link
Contributor

Hey @galuszkak — the listEvents extensions and the summary-filter bug fix look great, those should stay as-is.

However, I'd like to rethink the approach of adding three separate tools (calendar.createFocusTime, calendar.createOutOfOffice, calendar.setWorkingLocation). Instead, let's extend the existing calendar.createEvent with an eventType enum and the relevant type-specific optional properties.

Why:

  • The Google Calendar API itself uses a single events.insert endpoint with an eventType field — our tool surface should mirror that rather than fan out into per-type tools.
  • Three new tools means tool sprawl for LLM clients that need to choose between four creation tools, and every new event type Google adds would mean yet another tool.
  • The three new service methods duplicate ~200 lines of logic (calendar ID resolution, datetime validation, events.insert, error/success response construction) that createEvent already handles.

What I'd like to see instead:

Add optional fields to calendar.createEvent's schema:

  • eventType?: 'default' | 'focusTime' | 'outOfOffice' | 'workingLocation'
  • focusTimeProperties?: { chatStatus, autoDeclineMode, declineMessage }
  • outOfOfficeProperties?: { autoDeclineMode, declineMessage }
  • workingLocationProperties?: { type, homeOffice, officeLocation, customLocation }

Then have createEvent set transparency and visibility automatically based on the eventType (e.g., workingLocationtransparency: 'transparent', visibility: 'public'; focusTime/outOfOfficetransparency: 'opaque').

Additionally, this is a good opportunity to fix #177 in the same PR — createEvent's start/end schema currently requires dateTime, which prevents all-day event creation. Your setWorkingLocation already handles both date and dateTime — we need that same flexibility in createEvent itself. Making start and end accept either date or dateTime (both optional, at least one required) would unblock all-day events for all event types, including OOO and working location.

To summarize the ask:

  1. Remove the three new tools and fold their functionality into createEvent
  2. Update createEvent's start/end to support both date and dateTime (fixes calendar.createEvent tool enforces dateTime requirement, preventing all-day event creation #177)
  3. Keep the listEvents changes (eventTypes filter, expanded fields, summary-filter fix)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants