chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type CalendarAttendee,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
type GoogleCalendarCreateParams,
|
||||
type GoogleCalendarCreateResponse,
|
||||
type GoogleCalendarEventRequestBody,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import {
|
||||
assertRecurringTimeZone,
|
||||
buildEventDateTime,
|
||||
buildGoogleMeetConferenceData,
|
||||
normalizeAttendees,
|
||||
normalizeRecurrence,
|
||||
} from '@/tools/google_calendar/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createTool: ToolConfig<GoogleCalendarCreateParams, GoogleCalendarCreateResponse> = {
|
||||
id: 'google_calendar_create',
|
||||
name: 'Google Calendar Create Event',
|
||||
description: 'Create a new event in Google Calendar',
|
||||
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)',
|
||||
},
|
||||
summary: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event title/summary',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event description',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event location',
|
||||
},
|
||||
startDateTime: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Start time. Use a datetime with timezone offset (2025-06-03T10:00:00-08:00) or a date (2025-06-03) for an all-day event',
|
||||
},
|
||||
endDateTime: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'End time. Use a datetime with timezone offset (2025-06-03T11:00:00-08:00) or a date (2025-06-04) for an all-day event',
|
||||
},
|
||||
timeZone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'IANA time zone (e.g., America/Los_Angeles). Used as-is when provided. For recurring events a time zone is required to expand the recurrence correctly; for one-off events it is only needed when the datetime omits a UTC offset (a naive datetime defaults to America/Los_Angeles).',
|
||||
},
|
||||
attendees: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Array of attendee email addresses',
|
||||
},
|
||||
recurrence: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Recurrence rule(s) in RFC 5545 format (e.g., RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR). Separate multiple rules with newlines.',
|
||||
},
|
||||
addGoogleMeet: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attach a Google Meet video conference link to the event',
|
||||
},
|
||||
sendUpdates: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'How to send updates to attendees: all, externalOnly, or none',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarCreateParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sendUpdates !== undefined) {
|
||||
queryParams.append('sendUpdates', params.sendUpdates)
|
||||
}
|
||||
if (params.addGoogleMeet) {
|
||||
queryParams.append('conferenceDataVersion', '1')
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params: GoogleCalendarCreateParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleCalendarCreateParams): GoogleCalendarEventRequestBody => {
|
||||
const recurrence = normalizeRecurrence(params.recurrence)
|
||||
const isRecurring = recurrence.length > 0
|
||||
|
||||
if (isRecurring) {
|
||||
assertRecurringTimeZone([params.startDateTime, params.endDateTime], params.timeZone)
|
||||
}
|
||||
|
||||
const eventData: GoogleCalendarEventRequestBody = {
|
||||
summary: params.summary,
|
||||
start: buildEventDateTime(params.startDateTime, params.timeZone),
|
||||
end: buildEventDateTime(params.endDateTime, params.timeZone),
|
||||
}
|
||||
|
||||
if (params.description) {
|
||||
eventData.description = params.description
|
||||
}
|
||||
|
||||
if (params.location) {
|
||||
eventData.location = params.location
|
||||
}
|
||||
|
||||
const attendees = normalizeAttendees(params.attendees)
|
||||
if (attendees.length > 0) {
|
||||
eventData.attendees = attendees
|
||||
}
|
||||
|
||||
if (isRecurring) {
|
||||
eventData.recurrence = recurrence
|
||||
}
|
||||
|
||||
if (params.addGoogleMeet) {
|
||||
eventData.conferenceData = buildGoogleMeetConferenceData()
|
||||
}
|
||||
|
||||
return eventData
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Event "${data.summary}" created successfully`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
hangoutLink: data.hangoutLink,
|
||||
status: data.status,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
recurrence: data.recurrence,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees,
|
||||
creator: data.creator,
|
||||
organizer: data.organizer,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Event creation confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Created event metadata including ID, status, Meet link, and details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarCreateV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
htmlLink: string
|
||||
hangoutLink: string | null
|
||||
status: string
|
||||
summary: string | null
|
||||
description: string | null
|
||||
location: string | null
|
||||
recurrence: string[] | null
|
||||
start: GoogleCalendarApiEventResponse['start']
|
||||
end: GoogleCalendarApiEventResponse['end']
|
||||
attendees: CalendarAttendee[] | null
|
||||
creator: GoogleCalendarApiEventResponse['creator'] | null
|
||||
organizer: GoogleCalendarApiEventResponse['organizer'] | null
|
||||
}
|
||||
}
|
||||
|
||||
export const createV2Tool: ToolConfig<GoogleCalendarCreateParams, GoogleCalendarCreateV2Response> =
|
||||
{
|
||||
id: 'google_calendar_create_v2',
|
||||
name: 'Google Calendar Create Event',
|
||||
description: 'Create a new event in Google Calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: createTool.oauth,
|
||||
params: createTool.params,
|
||||
request: createTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
hangoutLink: data.hangoutLink ?? null,
|
||||
status: data.status,
|
||||
summary: data.summary ?? null,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
recurrence: data.recurrence ?? null,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees ?? null,
|
||||
creator: data.creator ?? null,
|
||||
organizer: data.organizer ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Event ID' },
|
||||
htmlLink: { type: 'string', description: 'Event link' },
|
||||
hangoutLink: { type: 'string', description: 'Google Meet link', optional: true },
|
||||
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 },
|
||||
recurrence: { type: 'json', description: 'Recurrence rules', 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', optional: true },
|
||||
organizer: { type: 'json', description: 'Event organizer', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiCalendarResponse,
|
||||
type GoogleCalendarCreateCalendarParams,
|
||||
type GoogleCalendarCreateCalendarResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createCalendarTool: ToolConfig<
|
||||
GoogleCalendarCreateCalendarParams,
|
||||
GoogleCalendarCreateCalendarResponse
|
||||
> = {
|
||||
id: 'google_calendar_create_calendar',
|
||||
name: 'Google Calendar Create Calendar',
|
||||
description: 'Create a new secondary calendar',
|
||||
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',
|
||||
},
|
||||
summary: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Title of the new calendar',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the new calendar',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Geographic location of the calendar as free-form text',
|
||||
},
|
||||
timeZone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Time zone of the calendar as an IANA name (e.g., America/Los_Angeles)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `${CALENDAR_API_BASE}/calendars`,
|
||||
method: 'POST',
|
||||
headers: (params: GoogleCalendarCreateCalendarParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleCalendarCreateCalendarParams) => {
|
||||
const body: Record<string, string> = { summary: params.summary }
|
||||
if (params.description) body.description = params.description
|
||||
if (params.location) body.location = params.location
|
||||
if (params.timeZone) body.timeZone = params.timeZone
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiCalendarResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Calendar "${data.summary}" created successfully`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
timeZone: data.timeZone,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Calendar creation confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Created calendar metadata (id, summary, description, location, timeZone)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarCreateCalendarV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
summary: string
|
||||
description: string | null
|
||||
location: string | null
|
||||
timeZone: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const createCalendarV2Tool: ToolConfig<
|
||||
GoogleCalendarCreateCalendarParams,
|
||||
GoogleCalendarCreateCalendarV2Response
|
||||
> = {
|
||||
id: 'google_calendar_create_calendar_v2',
|
||||
name: 'Google Calendar Create Calendar',
|
||||
description: 'Create a new secondary calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: createCalendarTool.oauth,
|
||||
params: createCalendarTool.params,
|
||||
request: createCalendarTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiCalendarResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
summary: data.summary,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
timeZone: data.timeZone ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Calendar ID' },
|
||||
summary: { type: 'string', description: 'Calendar title' },
|
||||
description: { type: 'string', description: 'Calendar description', optional: true },
|
||||
location: { type: 'string', description: 'Calendar location', optional: true },
|
||||
timeZone: { type: 'string', description: 'Calendar time zone', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { CALENDAR_API_BASE, type GoogleCalendarDeleteParams } from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GoogleCalendarDeleteResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
eventId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteTool: ToolConfig<GoogleCalendarDeleteParams, GoogleCalendarDeleteResponse> = {
|
||||
id: 'google_calendar_delete',
|
||||
name: 'Google Calendar Delete Event',
|
||||
description: 'Delete an event from Google Calendar',
|
||||
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)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Calendar event ID to delete',
|
||||
},
|
||||
sendUpdates: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'How to send updates to attendees: all, externalOnly, or none',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarDeleteParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sendUpdates !== undefined) {
|
||||
queryParams.append('sendUpdates', params.sendUpdates)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params: GoogleCalendarDeleteParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (response.status === 204 || response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Event successfully deleted`,
|
||||
metadata: {
|
||||
eventId: params?.eventId || '',
|
||||
deleted: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.error?.message || 'Failed to delete event')
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Event deletion confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Deletion details including event ID',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarDeleteV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
eventId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteV2Tool: ToolConfig<GoogleCalendarDeleteParams, GoogleCalendarDeleteV2Response> =
|
||||
{
|
||||
id: 'google_calendar_delete_v2',
|
||||
name: 'Google Calendar Delete Event',
|
||||
description: 'Delete an event from Google Calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: deleteTool.oauth,
|
||||
params: deleteTool.params,
|
||||
request: deleteTool.request,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (response.status === 204 || response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
eventId: params?.eventId || '',
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.error?.message || 'Failed to delete event')
|
||||
},
|
||||
outputs: {
|
||||
eventId: { type: 'string', description: 'Deleted event ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { CALENDAR_API_BASE } from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface GoogleCalendarDeleteCalendarParams {
|
||||
accessToken: string
|
||||
calendarId: string
|
||||
}
|
||||
|
||||
interface GoogleCalendarDeleteCalendarResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
calendarId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buildDeleteCalendarUrl = (params: GoogleCalendarDeleteCalendarParams) =>
|
||||
`${CALENDAR_API_BASE}/calendars/${encodeURIComponent(params.calendarId.trim())}`
|
||||
|
||||
export const deleteCalendarTool: ToolConfig<
|
||||
GoogleCalendarDeleteCalendarParams,
|
||||
GoogleCalendarDeleteCalendarResponse
|
||||
> = {
|
||||
id: 'google_calendar_delete_calendar',
|
||||
name: 'Google Calendar Delete Calendar',
|
||||
description: 'Permanently delete a secondary calendar (not the primary calendar)',
|
||||
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: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Secondary calendar ID to delete (e.g., calendar@group.calendar.google.com). The primary calendar cannot be deleted.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: buildDeleteCalendarUrl,
|
||||
method: 'DELETE',
|
||||
headers: (params: GoogleCalendarDeleteCalendarParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (response.status === 204 || response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: 'Calendar successfully deleted',
|
||||
metadata: {
|
||||
calendarId: params?.calendarId || '',
|
||||
deleted: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = await response.json().catch(() => null)
|
||||
throw new Error(errorData?.error?.message || 'Failed to delete calendar')
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Calendar deletion confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Deletion details including calendar ID',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarDeleteCalendarV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
calendarId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteCalendarV2Tool: ToolConfig<
|
||||
GoogleCalendarDeleteCalendarParams,
|
||||
GoogleCalendarDeleteCalendarV2Response
|
||||
> = {
|
||||
id: 'google_calendar_delete_calendar_v2',
|
||||
name: 'Google Calendar Delete Calendar',
|
||||
description: 'Permanently delete a secondary calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: deleteCalendarTool.oauth,
|
||||
params: deleteCalendarTool.params,
|
||||
request: deleteCalendarTool.request,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (response.status === 204 || response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
calendarId: params?.calendarId || '',
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = await response.json().catch(() => null)
|
||||
throw new Error(errorData?.error?.message || 'Failed to delete calendar')
|
||||
},
|
||||
outputs: {
|
||||
calendarId: { type: 'string', description: 'Deleted calendar ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiFreeBusyResponse,
|
||||
type GoogleCalendarFreeBusyParams,
|
||||
type GoogleCalendarFreeBusyResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const freebusyTool: ToolConfig<
|
||||
GoogleCalendarFreeBusyParams,
|
||||
GoogleCalendarFreeBusyResponse
|
||||
> = {
|
||||
id: 'google_calendar_freebusy',
|
||||
name: 'Google Calendar Free/Busy',
|
||||
description: 'Query free/busy information for one or more Google Calendars',
|
||||
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',
|
||||
},
|
||||
calendarIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated calendar IDs to query (e.g., "primary,other@example.com")',
|
||||
},
|
||||
timeMin: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start of the time range (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z)',
|
||||
},
|
||||
timeMax: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the time range (RFC3339 timestamp, e.g., 2025-06-04T00:00:00Z)',
|
||||
},
|
||||
timeZone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IANA time zone (e.g., "UTC", "America/New_York"). Defaults to UTC.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `${CALENDAR_API_BASE}/freeBusy`,
|
||||
method: 'POST',
|
||||
headers: (params: GoogleCalendarFreeBusyParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleCalendarFreeBusyParams) => {
|
||||
const ids = params.calendarIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
timeMin: params.timeMin,
|
||||
timeMax: params.timeMax,
|
||||
timeZone: params.timeZone || 'UTC',
|
||||
items: ids.map((id) => ({ id })),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiFreeBusyResponse = await response.json()
|
||||
|
||||
const calendarIds = Object.keys(data.calendars || {})
|
||||
const totalBusy = calendarIds.reduce((sum, id) => {
|
||||
return sum + (data.calendars[id]?.busy?.length || 0)
|
||||
}, 0)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Found ${totalBusy} busy period${totalBusy !== 1 ? 's' : ''} across ${calendarIds.length} calendar${calendarIds.length !== 1 ? 's' : ''}`,
|
||||
metadata: {
|
||||
timeMin: data.timeMin,
|
||||
timeMax: data.timeMax,
|
||||
calendars: data.calendars,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Summary of free/busy results' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Free/busy data with time range and per-calendar busy periods',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarFreeBusyV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
timeMin: string
|
||||
timeMax: string
|
||||
calendars: Record<
|
||||
string,
|
||||
{
|
||||
busy: Array<{ start: string; end: string }>
|
||||
errors?: Array<{ domain: string; reason: string }>
|
||||
}
|
||||
>
|
||||
}
|
||||
}
|
||||
|
||||
export const freebusyV2Tool: ToolConfig<
|
||||
GoogleCalendarFreeBusyParams,
|
||||
GoogleCalendarFreeBusyV2Response
|
||||
> = {
|
||||
id: 'google_calendar_freebusy_v2',
|
||||
name: 'Google Calendar Free/Busy',
|
||||
description:
|
||||
'Query free/busy information for one or more Google Calendars. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: freebusyTool.oauth,
|
||||
params: freebusyTool.params,
|
||||
request: freebusyTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiFreeBusyResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
timeMin: data.timeMin,
|
||||
timeMax: data.timeMax,
|
||||
calendars: data.calendars,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
timeMin: { type: 'string', description: 'Start of the queried time range' },
|
||||
timeMax: { type: 'string', description: 'End of the queried time range' },
|
||||
calendars: {
|
||||
type: 'json',
|
||||
description: 'Per-calendar free/busy data with busy periods and any errors',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
type GoogleCalendarGetParams,
|
||||
type GoogleCalendarGetResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getTool: ToolConfig<GoogleCalendarGetParams, GoogleCalendarGetResponse> = {
|
||||
id: 'google_calendar_get',
|
||||
name: 'Google Calendar Get Event',
|
||||
description: 'Get a specific event from Google Calendar',
|
||||
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)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Calendar event ID to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarGetParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GoogleCalendarGetParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Retrieved event "${data.summary}"`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
status: data.status,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees,
|
||||
creator: data.creator,
|
||||
organizer: data.organizer,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Event retrieval confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Event details including ID, status, times, and attendees',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarGetV2Response {
|
||||
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 getV2Tool: ToolConfig<GoogleCalendarGetParams, GoogleCalendarGetV2Response> = {
|
||||
id: 'google_calendar_get_v2',
|
||||
name: 'Google Calendar Get Event',
|
||||
description: 'Get a specific event from Google Calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: getTool.oauth,
|
||||
params: getTool.params,
|
||||
request: getTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
status: data.status,
|
||||
summary: data.summary ?? null,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees ?? null,
|
||||
creator: data.creator,
|
||||
organizer: data.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' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { createTool, createV2Tool } from '@/tools/google_calendar/create'
|
||||
import { createCalendarTool, createCalendarV2Tool } from '@/tools/google_calendar/create_calendar'
|
||||
import { deleteTool, deleteV2Tool } from '@/tools/google_calendar/delete'
|
||||
import { deleteCalendarTool, deleteCalendarV2Tool } from '@/tools/google_calendar/delete_calendar'
|
||||
import { freebusyTool, freebusyV2Tool } from '@/tools/google_calendar/freebusy'
|
||||
import { getTool, getV2Tool } from '@/tools/google_calendar/get'
|
||||
import { instancesTool, instancesV2Tool } from '@/tools/google_calendar/instances'
|
||||
import { inviteTool, inviteV2Tool } from '@/tools/google_calendar/invite'
|
||||
import { listTool, listV2Tool } from '@/tools/google_calendar/list'
|
||||
import { listAclTool, listAclV2Tool } from '@/tools/google_calendar/list_acl'
|
||||
import { listCalendarsTool, listCalendarsV2Tool } from '@/tools/google_calendar/list_calendars'
|
||||
import { moveTool, moveV2Tool } from '@/tools/google_calendar/move'
|
||||
import { quickAddTool, quickAddV2Tool } from '@/tools/google_calendar/quick_add'
|
||||
import { shareCalendarTool, shareCalendarV2Tool } from '@/tools/google_calendar/share_calendar'
|
||||
import {
|
||||
unshareCalendarTool,
|
||||
unshareCalendarV2Tool,
|
||||
} from '@/tools/google_calendar/unshare_calendar'
|
||||
import { updateTool, updateV2Tool } from '@/tools/google_calendar/update'
|
||||
import { updateAclTool, updateAclV2Tool } from '@/tools/google_calendar/update_acl'
|
||||
import { updateCalendarTool, updateCalendarV2Tool } from '@/tools/google_calendar/update_calendar'
|
||||
|
||||
export const googleCalendarCreateTool = createTool
|
||||
export const googleCalendarCreateCalendarTool = createCalendarTool
|
||||
export const googleCalendarDeleteTool = deleteTool
|
||||
export const googleCalendarDeleteCalendarTool = deleteCalendarTool
|
||||
export const googleCalendarFreeBusyTool = freebusyTool
|
||||
export const googleCalendarGetTool = getTool
|
||||
export const googleCalendarInstancesTool = instancesTool
|
||||
export const googleCalendarInviteTool = inviteTool
|
||||
export const googleCalendarListTool = listTool
|
||||
export const googleCalendarListAclTool = listAclTool
|
||||
export const googleCalendarListCalendarsTool = listCalendarsTool
|
||||
export const googleCalendarMoveTool = moveTool
|
||||
export const googleCalendarQuickAddTool = quickAddTool
|
||||
export const googleCalendarShareCalendarTool = shareCalendarTool
|
||||
export const googleCalendarUnshareCalendarTool = unshareCalendarTool
|
||||
export const googleCalendarUpdateTool = updateTool
|
||||
export const googleCalendarUpdateAclTool = updateAclTool
|
||||
export const googleCalendarUpdateCalendarTool = updateCalendarTool
|
||||
|
||||
export const googleCalendarCreateV2Tool = createV2Tool
|
||||
export const googleCalendarCreateCalendarV2Tool = createCalendarV2Tool
|
||||
export const googleCalendarDeleteV2Tool = deleteV2Tool
|
||||
export const googleCalendarDeleteCalendarV2Tool = deleteCalendarV2Tool
|
||||
export const googleCalendarFreeBusyV2Tool = freebusyV2Tool
|
||||
export const googleCalendarGetV2Tool = getV2Tool
|
||||
export const googleCalendarInstancesV2Tool = instancesV2Tool
|
||||
export const googleCalendarInviteV2Tool = inviteV2Tool
|
||||
export const googleCalendarListV2Tool = listV2Tool
|
||||
export const googleCalendarListAclV2Tool = listAclV2Tool
|
||||
export const googleCalendarListCalendarsV2Tool = listCalendarsV2Tool
|
||||
export const googleCalendarMoveV2Tool = moveV2Tool
|
||||
export const googleCalendarQuickAddV2Tool = quickAddV2Tool
|
||||
export const googleCalendarShareCalendarV2Tool = shareCalendarV2Tool
|
||||
export const googleCalendarUnshareCalendarV2Tool = unshareCalendarV2Tool
|
||||
export const googleCalendarUpdateV2Tool = updateV2Tool
|
||||
export const googleCalendarUpdateAclV2Tool = updateAclV2Tool
|
||||
export const googleCalendarUpdateCalendarV2Tool = updateCalendarV2Tool
|
||||
@@ -0,0 +1,288 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GoogleCalendarInstancesParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
eventId: string
|
||||
timeMin?: string
|
||||
timeMax?: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
showDeleted?: boolean
|
||||
}
|
||||
|
||||
interface GoogleCalendarInstancesResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
nextPageToken?: string
|
||||
timeZone: string
|
||||
instances: Array<{
|
||||
id: string
|
||||
htmlLink: string
|
||||
status: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
start: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
end: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
attendees?: Array<{
|
||||
email: string
|
||||
displayName?: string
|
||||
responseStatus: string
|
||||
}>
|
||||
creator?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
organizer?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
recurringEventId: string
|
||||
originalStartTime: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface InstanceApiResponse {
|
||||
kind: string
|
||||
etag: string
|
||||
summary: string
|
||||
description?: string
|
||||
updated: string
|
||||
timeZone: string
|
||||
accessRole: string
|
||||
nextPageToken?: string
|
||||
items: Array<
|
||||
GoogleCalendarApiEventResponse & {
|
||||
recurringEventId: string
|
||||
originalStartTime: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
export const instancesTool: ToolConfig<
|
||||
GoogleCalendarInstancesParams,
|
||||
GoogleCalendarInstancesResponse
|
||||
> = {
|
||||
id: 'google_calendar_instances',
|
||||
name: 'Google Calendar Get Instances',
|
||||
description: 'Get instances of a recurring event from Google Calendar',
|
||||
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)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Recurring event ID to get instances of',
|
||||
},
|
||||
timeMin: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lower bound for instances (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z)',
|
||||
},
|
||||
timeMax: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Upper bound for instances (RFC3339 timestamp, e.g., 2025-06-04T00:00:00Z)',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of instances to return (default 250, max 2500)',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Token for retrieving subsequent pages of results',
|
||||
},
|
||||
showDeleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Include deleted instances',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarInstancesParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
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.pageToken) queryParams.append('pageToken', params.pageToken)
|
||||
if (params.showDeleted !== undefined)
|
||||
queryParams.append('showDeleted', params.showDeleted.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}/instances${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GoogleCalendarInstancesParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: InstanceApiResponse = await response.json()
|
||||
const instances = data.items || []
|
||||
const instancesCount = instances.length
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Found ${instancesCount} instance${instancesCount !== 1 ? 's' : ''} of the recurring event`,
|
||||
metadata: {
|
||||
nextPageToken: data.nextPageToken,
|
||||
timeZone: data.timeZone,
|
||||
instances: instances.map((instance) => ({
|
||||
id: instance.id,
|
||||
htmlLink: instance.htmlLink,
|
||||
status: instance.status,
|
||||
summary: instance.summary || 'No title',
|
||||
description: instance.description,
|
||||
location: instance.location,
|
||||
start: instance.start,
|
||||
end: instance.end,
|
||||
attendees: instance.attendees,
|
||||
creator: instance.creator,
|
||||
organizer: instance.organizer,
|
||||
recurringEventId: instance.recurringEventId,
|
||||
originalStartTime: instance.originalStartTime,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Summary of found instances count' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'List of recurring event instances with pagination tokens',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarInstancesV2Instance {
|
||||
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']
|
||||
recurringEventId: string
|
||||
originalStartTime: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface GoogleCalendarInstancesV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
nextPageToken: string | null
|
||||
timeZone: string | null
|
||||
instances: GoogleCalendarInstancesV2Instance[]
|
||||
}
|
||||
}
|
||||
|
||||
export const instancesV2Tool: ToolConfig<
|
||||
GoogleCalendarInstancesParams,
|
||||
GoogleCalendarInstancesV2Response
|
||||
> = {
|
||||
id: 'google_calendar_instances_v2',
|
||||
name: 'Google Calendar Get Instances',
|
||||
description:
|
||||
'Get instances of a recurring event from Google Calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: instancesTool.oauth,
|
||||
params: instancesTool.params,
|
||||
request: instancesTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: InstanceApiResponse = await response.json()
|
||||
const instances = data.items || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
nextPageToken: data.nextPageToken ?? null,
|
||||
timeZone: data.timeZone ?? null,
|
||||
instances: instances.map((instance) => ({
|
||||
id: instance.id,
|
||||
htmlLink: instance.htmlLink,
|
||||
status: instance.status,
|
||||
summary: instance.summary ?? null,
|
||||
description: instance.description ?? null,
|
||||
location: instance.location ?? null,
|
||||
start: instance.start,
|
||||
end: instance.end,
|
||||
attendees: instance.attendees ?? null,
|
||||
creator: instance.creator,
|
||||
organizer: instance.organizer,
|
||||
recurringEventId: instance.recurringEventId,
|
||||
originalStartTime: instance.originalStartTime,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
nextPageToken: { type: 'string', description: 'Next page token', optional: true },
|
||||
timeZone: { type: 'string', description: 'Calendar time zone', optional: true },
|
||||
instances: { type: 'json', description: 'List of recurring event instances' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type CalendarAttendee,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
type GoogleCalendarInviteParams,
|
||||
type GoogleCalendarInviteResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import { normalizeAttendees } from '@/tools/google_calendar/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface InviteResult {
|
||||
data: GoogleCalendarApiEventResponse
|
||||
totalAttendees: number
|
||||
newAttendeesAdded: number
|
||||
shouldReplace: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The Google Calendar update method replaces the entire event resource, so to invite
|
||||
* attendees we read the existing event, merge the attendee list, then PUT it back.
|
||||
*/
|
||||
async function inviteAttendees(
|
||||
response: Response,
|
||||
params: GoogleCalendarInviteParams | undefined
|
||||
): Promise<InviteResult> {
|
||||
const existingEvent: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
if (!existingEvent.start || !existingEvent.end || !existingEvent.summary) {
|
||||
throw new Error('Existing event is missing required fields (start, end, or summary)')
|
||||
}
|
||||
|
||||
const newAttendeeList = normalizeAttendees(params?.attendees).map((attendee) => attendee.email)
|
||||
const existingAttendees: CalendarAttendee[] = existingEvent.attendees ?? []
|
||||
const shouldReplace =
|
||||
params?.replaceExisting === true || String(params?.replaceExisting) === 'true'
|
||||
|
||||
const existingEmails = new Set(
|
||||
existingAttendees.map((attendee) => attendee.email?.toLowerCase() ?? '')
|
||||
)
|
||||
const newAttendeesAdded = shouldReplace
|
||||
? newAttendeeList.length
|
||||
: newAttendeeList.filter((email) => !existingEmails.has(email.toLowerCase())).length
|
||||
|
||||
let finalAttendees: CalendarAttendee[]
|
||||
if (shouldReplace) {
|
||||
finalAttendees = newAttendeeList.map((email) => ({ email, responseStatus: 'needsAction' }))
|
||||
} else {
|
||||
finalAttendees = [...existingAttendees]
|
||||
for (const email of newAttendeeList) {
|
||||
if (!existingEmails.has(email.toLowerCase())) {
|
||||
finalAttendees.push({ email, responseStatus: 'needsAction' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedEvent: Record<string, unknown> = { ...existingEvent, attendees: finalAttendees }
|
||||
const readOnlyFields = [
|
||||
'id',
|
||||
'etag',
|
||||
'kind',
|
||||
'created',
|
||||
'updated',
|
||||
'htmlLink',
|
||||
'iCalUID',
|
||||
'creator',
|
||||
'organizer',
|
||||
]
|
||||
for (const field of readOnlyFields) {
|
||||
delete updatedEvent[field]
|
||||
}
|
||||
|
||||
const calendarId = params?.calendarId?.trim() || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('sendUpdates', params?.sendUpdates ?? 'all')
|
||||
const putUrl = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params?.eventId?.trim() ?? '')}?${queryParams.toString()}`
|
||||
|
||||
const putResponse = await fetch(putUrl, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: `Bearer ${params?.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(updatedEvent),
|
||||
})
|
||||
|
||||
if (!putResponse.ok) {
|
||||
const errorData = await putResponse.json().catch(() => null)
|
||||
throw new Error(errorData?.error?.message || 'Failed to invite attendees to calendar event')
|
||||
}
|
||||
|
||||
const data: GoogleCalendarApiEventResponse = await putResponse.json()
|
||||
return {
|
||||
data,
|
||||
totalAttendees: data.attendees?.length ?? 0,
|
||||
newAttendeesAdded,
|
||||
shouldReplace,
|
||||
}
|
||||
}
|
||||
|
||||
export const inviteTool: ToolConfig<GoogleCalendarInviteParams, GoogleCalendarInviteResponse> = {
|
||||
id: 'google_calendar_invite',
|
||||
name: 'Google Calendar Invite Attendees',
|
||||
description: 'Invite attendees to an existing Google Calendar event',
|
||||
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)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Calendar event ID to invite attendees to',
|
||||
},
|
||||
attendees: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Array of attendee email addresses to invite',
|
||||
},
|
||||
sendUpdates: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'How to send updates to attendees: all, externalOnly, or none (defaults to all)',
|
||||
},
|
||||
replaceExisting: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Whether to replace existing attendees or add to them (defaults to false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarInviteParams) => {
|
||||
const calendarId = params.calendarId?.trim() || 'primary'
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GoogleCalendarInviteParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const { data, totalAttendees, newAttendeesAdded, shouldReplace } = await inviteAttendees(
|
||||
response,
|
||||
params
|
||||
)
|
||||
|
||||
let baseMessage: string
|
||||
if (shouldReplace) {
|
||||
baseMessage = `Successfully updated event "${data.summary}" with ${totalAttendees} attendee${totalAttendees !== 1 ? 's' : ''}`
|
||||
} else if (newAttendeesAdded > 0) {
|
||||
baseMessage = `Successfully added ${newAttendeesAdded} new attendee${newAttendeesAdded !== 1 ? 's' : ''} to event "${data.summary}" (total: ${totalAttendees})`
|
||||
} else {
|
||||
baseMessage = `No new attendees added to event "${data.summary}" - all specified attendees were already invited (total: ${totalAttendees})`
|
||||
}
|
||||
|
||||
const emailNote =
|
||||
params?.sendUpdates !== 'none'
|
||||
? ' Email invitations are being sent asynchronously - delivery may take a few minutes and depends on recipients’ Google Calendar settings.'
|
||||
: ' No email notifications will be sent as requested.'
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: baseMessage + emailNote,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
status: data.status,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees,
|
||||
creator: data.creator,
|
||||
organizer: data.organizer,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Attendee invitation confirmation message with email delivery status',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Updated event metadata including attendee list and details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarInviteV2Response {
|
||||
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: CalendarAttendee[] | null
|
||||
creator: GoogleCalendarApiEventResponse['creator'] | null
|
||||
organizer: GoogleCalendarApiEventResponse['organizer'] | null
|
||||
}
|
||||
}
|
||||
|
||||
export const inviteV2Tool: ToolConfig<GoogleCalendarInviteParams, GoogleCalendarInviteV2Response> =
|
||||
{
|
||||
id: 'google_calendar_invite_v2',
|
||||
name: 'Google Calendar Invite Attendees',
|
||||
description:
|
||||
'Invite attendees to an existing Google Calendar event. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: inviteTool.oauth,
|
||||
params: inviteTool.params,
|
||||
request: inviteTool.request,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const { data } = await inviteAttendees(response, params)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
status: data.status,
|
||||
summary: data.summary ?? null,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees ?? null,
|
||||
creator: data.creator ?? null,
|
||||
organizer: data.organizer ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
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', optional: true },
|
||||
organizer: { type: 'json', description: 'Event organizer', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
type GoogleCalendarApiListResponse,
|
||||
type GoogleCalendarListParams,
|
||||
type GoogleCalendarListResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listTool: ToolConfig<GoogleCalendarListParams, GoogleCalendarListResponse> = {
|
||||
id: 'google_calendar_list',
|
||||
name: 'Google Calendar List Events',
|
||||
description: 'List events from Google Calendar',
|
||||
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)',
|
||||
},
|
||||
timeMin: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lower bound for events (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z)',
|
||||
},
|
||||
timeMax: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Upper bound for events (RFC3339 timestamp, e.g., 2025-06-04T00:00:00Z)',
|
||||
},
|
||||
q: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Free-text search across event summary, description, location, attendees, and organizer',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of events to return (max 2500)',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Token for retrieving the next page of results',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Order of events: startTime (chronological, the default) or updated (last-modified). startTime is always valid here because singleEvents is set.',
|
||||
},
|
||||
showDeleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Include deleted events',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarListParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.timeMin) queryParams.append('timeMin', params.timeMin)
|
||||
if (params.timeMax) queryParams.append('timeMax', params.timeMax)
|
||||
if (params.q) queryParams.append('q', params.q)
|
||||
if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString())
|
||||
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
|
||||
queryParams.append('singleEvents', 'true')
|
||||
queryParams.append('orderBy', params.orderBy || 'startTime')
|
||||
if (params.showDeleted !== undefined)
|
||||
queryParams.append('showDeleted', params.showDeleted.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GoogleCalendarListParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiListResponse = await response.json()
|
||||
const events = data.items || []
|
||||
const eventsCount = events.length
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Found ${eventsCount} event${eventsCount !== 1 ? 's' : ''}`,
|
||||
metadata: {
|
||||
nextPageToken: data.nextPageToken,
|
||||
nextSyncToken: data.nextSyncToken,
|
||||
timeZone: data.timeZone,
|
||||
events: events.map((event: GoogleCalendarApiEventResponse) => ({
|
||||
id: event.id,
|
||||
htmlLink: event.htmlLink,
|
||||
status: event.status,
|
||||
summary: event.summary || 'No title',
|
||||
description: event.description,
|
||||
location: event.location,
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
attendees: event.attendees,
|
||||
creator: event.creator,
|
||||
organizer: event.organizer,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Summary of found events count' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'List of events with pagination tokens and event details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarListV2Event {
|
||||
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'] | null
|
||||
organizer: GoogleCalendarApiEventResponse['organizer'] | null
|
||||
}
|
||||
|
||||
interface GoogleCalendarListV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
nextPageToken: string | null
|
||||
timeZone: string | null
|
||||
events: GoogleCalendarListV2Event[]
|
||||
}
|
||||
}
|
||||
|
||||
export const listV2Tool: ToolConfig<GoogleCalendarListParams, GoogleCalendarListV2Response> = {
|
||||
id: 'google_calendar_list_v2',
|
||||
name: 'Google Calendar List Events',
|
||||
description: 'List events from Google Calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: listTool.oauth,
|
||||
params: listTool.params,
|
||||
request: listTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiListResponse = await response.json()
|
||||
const events = data.items || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
nextPageToken: data.nextPageToken ?? null,
|
||||
timeZone: data.timeZone ?? null,
|
||||
events: events.map((event: GoogleCalendarApiEventResponse) => ({
|
||||
id: event.id,
|
||||
htmlLink: event.htmlLink,
|
||||
status: event.status,
|
||||
summary: event.summary ?? null,
|
||||
description: event.description ?? null,
|
||||
location: event.location ?? null,
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
attendees: event.attendees ?? null,
|
||||
creator: event.creator,
|
||||
organizer: event.organizer,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
nextPageToken: { type: 'string', description: 'Next page token', optional: true },
|
||||
timeZone: { type: 'string', description: 'Calendar time zone', optional: true },
|
||||
events: { type: 'json', description: 'List of events' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiAclListResponse,
|
||||
type GoogleCalendarListAclParams,
|
||||
type GoogleCalendarListAclResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listAclTool: ToolConfig<GoogleCalendarListAclParams, GoogleCalendarListAclResponse> = {
|
||||
id: 'google_calendar_list_acl',
|
||||
name: 'Google Calendar List Sharing',
|
||||
description: 'List the access control rules (sharing) for a calendar',
|
||||
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: 'Calendar ID to inspect (e.g., primary or calendar@group.calendar.google.com)',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of ACL rules to return',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Token for retrieving subsequent pages of results',
|
||||
},
|
||||
showDeleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include deleted ACL rules (with role "none")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarListAclParams) => {
|
||||
const calendarId = params.calendarId?.trim() || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString())
|
||||
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
|
||||
if (params.showDeleted !== undefined)
|
||||
queryParams.append('showDeleted', params.showDeleted.toString())
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/acl${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GoogleCalendarListAclParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiAclListResponse = await response.json()
|
||||
const rules = data.items || []
|
||||
const rulesCount = rules.length
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Found ${rulesCount} sharing rule${rulesCount !== 1 ? 's' : ''}`,
|
||||
metadata: {
|
||||
nextPageToken: data.nextPageToken,
|
||||
rules: rules.map((rule) => ({
|
||||
id: rule.id,
|
||||
role: rule.role,
|
||||
scope: rule.scope,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Summary of found sharing rules count' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'List of ACL rules with pagination token',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarListAclV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
nextPageToken: string | null
|
||||
rules: Array<{ id: string; role: string; scope: { type: string; value?: string } }>
|
||||
}
|
||||
}
|
||||
|
||||
export const listAclV2Tool: ToolConfig<
|
||||
GoogleCalendarListAclParams,
|
||||
GoogleCalendarListAclV2Response
|
||||
> = {
|
||||
id: 'google_calendar_list_acl_v2',
|
||||
name: 'Google Calendar List Sharing',
|
||||
description:
|
||||
'List the access control rules (sharing) for a calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: listAclTool.oauth,
|
||||
params: listAclTool.params,
|
||||
request: listAclTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiAclListResponse = await response.json()
|
||||
const rules = data.items || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
nextPageToken: data.nextPageToken ?? null,
|
||||
rules: rules.map((rule) => ({
|
||||
id: rule.id,
|
||||
role: rule.role,
|
||||
scope: rule.scope,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
nextPageToken: { type: 'string', description: 'Next page token', optional: true },
|
||||
rules: {
|
||||
type: 'array',
|
||||
description: 'List of ACL rules',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'ACL rule ID' },
|
||||
role: { type: 'string', description: 'Access role' },
|
||||
scope: { type: 'json', description: 'Grantee scope (type and value)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
import { CALENDAR_API_BASE } from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GoogleCalendarListCalendarsParams {
|
||||
accessToken: string
|
||||
minAccessRole?: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
showDeleted?: boolean
|
||||
showHidden?: boolean
|
||||
}
|
||||
|
||||
interface CalendarListEntry {
|
||||
kind: string
|
||||
etag: string
|
||||
id: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone: string
|
||||
summaryOverride?: string
|
||||
colorId: string
|
||||
backgroundColor: string
|
||||
foregroundColor: string
|
||||
hidden?: boolean
|
||||
selected?: boolean
|
||||
accessRole: string
|
||||
defaultReminders: Array<{
|
||||
method: string
|
||||
minutes: number
|
||||
}>
|
||||
notificationSettings?: {
|
||||
notifications: Array<{
|
||||
type: string
|
||||
method: string
|
||||
}>
|
||||
}
|
||||
primary?: boolean
|
||||
deleted?: boolean
|
||||
conferenceProperties?: {
|
||||
allowedConferenceSolutionTypes: string[]
|
||||
}
|
||||
}
|
||||
|
||||
interface CalendarListApiResponse {
|
||||
kind: string
|
||||
etag: string
|
||||
nextPageToken?: string
|
||||
nextSyncToken?: string
|
||||
items: CalendarListEntry[]
|
||||
}
|
||||
|
||||
interface GoogleCalendarListCalendarsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
nextPageToken?: string
|
||||
calendars: Array<{
|
||||
id: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone: string
|
||||
accessRole: string
|
||||
backgroundColor: string
|
||||
foregroundColor: string
|
||||
primary?: boolean
|
||||
hidden?: boolean
|
||||
selected?: boolean
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const listCalendarsTool: ToolConfig<
|
||||
GoogleCalendarListCalendarsParams,
|
||||
GoogleCalendarListCalendarsResponse
|
||||
> = {
|
||||
id: 'google_calendar_list_calendars',
|
||||
name: 'Google Calendar List Calendars',
|
||||
description: "List all calendars in the user's calendar list",
|
||||
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',
|
||||
},
|
||||
minAccessRole: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Minimum access role for returned calendars: freeBusyReader, reader, writer, or owner',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of calendars to return (default 100, max 250)',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Token for retrieving subsequent pages of results',
|
||||
},
|
||||
showDeleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Include deleted calendars',
|
||||
},
|
||||
showHidden: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Include hidden calendars',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarListCalendarsParams) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.minAccessRole) queryParams.append('minAccessRole', params.minAccessRole)
|
||||
if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString())
|
||||
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
|
||||
if (params.showDeleted !== undefined)
|
||||
queryParams.append('showDeleted', params.showDeleted.toString())
|
||||
if (params.showHidden !== undefined)
|
||||
queryParams.append('showHidden', params.showHidden.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/users/me/calendarList${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: GoogleCalendarListCalendarsParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: CalendarListApiResponse = await response.json()
|
||||
const calendars = data.items || []
|
||||
const calendarsCount = calendars.length
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Found ${calendarsCount} calendar${calendarsCount !== 1 ? 's' : ''}`,
|
||||
metadata: {
|
||||
nextPageToken: data.nextPageToken,
|
||||
calendars: calendars.map((calendar) => ({
|
||||
id: calendar.id,
|
||||
summary: calendar.summaryOverride || calendar.summary,
|
||||
description: calendar.description,
|
||||
location: calendar.location,
|
||||
timeZone: calendar.timeZone,
|
||||
accessRole: calendar.accessRole,
|
||||
backgroundColor: calendar.backgroundColor,
|
||||
foregroundColor: calendar.foregroundColor,
|
||||
primary: calendar.primary,
|
||||
hidden: calendar.hidden,
|
||||
selected: calendar.selected,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Summary of found calendars count' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'List of calendars with their details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarListCalendarsV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
nextPageToken: string | null
|
||||
calendars: Array<{
|
||||
id: string
|
||||
summary: string
|
||||
description: string | null
|
||||
location: string | null
|
||||
timeZone: string
|
||||
accessRole: string
|
||||
backgroundColor: string
|
||||
foregroundColor: string
|
||||
primary: boolean | null
|
||||
hidden: boolean | null
|
||||
selected: boolean | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export const listCalendarsV2Tool: ToolConfig<
|
||||
GoogleCalendarListCalendarsParams,
|
||||
GoogleCalendarListCalendarsV2Response
|
||||
> = {
|
||||
id: 'google_calendar_list_calendars_v2',
|
||||
name: 'Google Calendar List Calendars',
|
||||
description: "List all calendars in the user's calendar list. Returns API-aligned fields only.",
|
||||
version: '2.0.0',
|
||||
oauth: listCalendarsTool.oauth,
|
||||
params: listCalendarsTool.params,
|
||||
request: listCalendarsTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: CalendarListApiResponse = await response.json()
|
||||
const calendars = data.items || []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
nextPageToken: data.nextPageToken ?? null,
|
||||
calendars: calendars.map((calendar) => ({
|
||||
id: calendar.id,
|
||||
summary: calendar.summaryOverride || calendar.summary,
|
||||
description: calendar.description ?? null,
|
||||
location: calendar.location ?? null,
|
||||
timeZone: calendar.timeZone,
|
||||
accessRole: calendar.accessRole,
|
||||
backgroundColor: calendar.backgroundColor,
|
||||
foregroundColor: calendar.foregroundColor,
|
||||
primary: calendar.primary ?? null,
|
||||
hidden: calendar.hidden ?? null,
|
||||
selected: calendar.selected ?? null,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
nextPageToken: { type: 'string', description: 'Next page token', optional: true },
|
||||
calendars: {
|
||||
type: 'array',
|
||||
description: 'List of calendars',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Calendar ID' },
|
||||
summary: { type: 'string', description: 'Calendar title' },
|
||||
description: { type: 'string', description: 'Calendar description', optional: true },
|
||||
location: { type: 'string', description: 'Calendar location', optional: true },
|
||||
timeZone: { type: 'string', description: 'Calendar time zone' },
|
||||
accessRole: { type: 'string', description: 'Access role for the calendar' },
|
||||
backgroundColor: { type: 'string', description: 'Calendar background color' },
|
||||
foregroundColor: { type: 'string', description: 'Calendar foreground color' },
|
||||
primary: {
|
||||
type: 'boolean',
|
||||
description: 'Whether this is the primary calendar',
|
||||
optional: true,
|
||||
},
|
||||
hidden: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the calendar is hidden',
|
||||
optional: true,
|
||||
},
|
||||
selected: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the calendar is selected',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GoogleCalendarMoveParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
eventId: string
|
||||
destinationCalendarId: string
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
}
|
||||
|
||||
interface GoogleCalendarMoveResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
htmlLink: string
|
||||
status: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
start: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
end: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
attendees?: Array<{
|
||||
email: string
|
||||
displayName?: string
|
||||
responseStatus: string
|
||||
}>
|
||||
creator?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
organizer?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const moveTool: ToolConfig<GoogleCalendarMoveParams, GoogleCalendarMoveResponse> = {
|
||||
id: 'google_calendar_move',
|
||||
name: 'Google Calendar Move Event',
|
||||
description: 'Move an event to a different calendar',
|
||||
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:
|
||||
'Source Google Calendar ID (e.g., primary or calendar@group.calendar.google.com)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Calendar event ID to move',
|
||||
},
|
||||
destinationCalendarId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Destination Google Calendar ID',
|
||||
},
|
||||
sendUpdates: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'How to send updates to attendees: all, externalOnly, or none',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarMoveParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
queryParams.append('destination', params.destinationCalendarId)
|
||||
|
||||
if (params.sendUpdates !== undefined) {
|
||||
queryParams.append('sendUpdates', params.sendUpdates)
|
||||
}
|
||||
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}/move?${queryParams.toString()}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params: GoogleCalendarMoveParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Event "${data.summary || 'Untitled'}" moved successfully`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
status: data.status,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees,
|
||||
creator: data.creator,
|
||||
organizer: data.organizer,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Event move confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Moved event metadata including new details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarMoveV2Response {
|
||||
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 moveV2Tool: ToolConfig<GoogleCalendarMoveParams, GoogleCalendarMoveV2Response> = {
|
||||
id: 'google_calendar_move_v2',
|
||||
name: 'Google Calendar Move Event',
|
||||
description: 'Move an event to a different calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: moveTool.oauth,
|
||||
params: moveTool.params,
|
||||
request: moveTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
status: data.status,
|
||||
summary: data.summary ?? null,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees ?? null,
|
||||
creator: data.creator,
|
||||
organizer: data.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' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
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' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiAclRule,
|
||||
type GoogleCalendarShareCalendarParams,
|
||||
type GoogleCalendarShareCalendarResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const buildAclBody = (params: GoogleCalendarShareCalendarParams) => {
|
||||
const scope: { type: string; value?: string } = { type: params.scopeType }
|
||||
if (params.scopeType !== 'default') {
|
||||
const value = params.scopeValue?.trim()
|
||||
if (!value) {
|
||||
throw new Error(
|
||||
`A grantee is required when scope type is "${params.scopeType}". Provide an email (user/group) or domain name in scopeValue.`
|
||||
)
|
||||
}
|
||||
scope.value = value
|
||||
}
|
||||
return { role: params.role, scope }
|
||||
}
|
||||
|
||||
const buildAclUrl = (params: GoogleCalendarShareCalendarParams) => {
|
||||
const calendarId = params.calendarId?.trim() || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.sendNotifications !== undefined) {
|
||||
queryParams.append('sendNotifications', String(params.sendNotifications))
|
||||
}
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/acl${queryString ? `?${queryString}` : ''}`
|
||||
}
|
||||
|
||||
export const shareCalendarTool: ToolConfig<
|
||||
GoogleCalendarShareCalendarParams,
|
||||
GoogleCalendarShareCalendarResponse
|
||||
> = {
|
||||
id: 'google_calendar_share_calendar',
|
||||
name: 'Google Calendar Share Calendar',
|
||||
description: 'Grant a user, group, or domain access to a calendar by creating an ACL rule',
|
||||
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: 'Calendar ID to share (e.g., primary or calendar@group.calendar.google.com)',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Access role to grant: freeBusyReader, reader, writer, or owner',
|
||||
},
|
||||
scopeType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Type of grantee: user, group, domain, or default (public)',
|
||||
},
|
||||
scopeValue: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Email (user/group), domain name (domain), or empty for default. Required unless scope type is default.',
|
||||
},
|
||||
sendNotifications: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Whether to send a notification email about the change. Defaults to true.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: buildAclUrl,
|
||||
method: 'POST',
|
||||
headers: (params: GoogleCalendarShareCalendarParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: buildAclBody,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiAclRule = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Granted ${data.role} access to ${data.scope?.value || data.scope?.type}`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
role: data.role,
|
||||
scope: data.scope,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Sharing confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Created ACL rule (id, role, scope)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarShareCalendarV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
role: string
|
||||
scope: { type: string; value?: string }
|
||||
}
|
||||
}
|
||||
|
||||
export const shareCalendarV2Tool: ToolConfig<
|
||||
GoogleCalendarShareCalendarParams,
|
||||
GoogleCalendarShareCalendarV2Response
|
||||
> = {
|
||||
id: 'google_calendar_share_calendar_v2',
|
||||
name: 'Google Calendar Share Calendar',
|
||||
description:
|
||||
'Grant a user, group, or domain access to a calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: shareCalendarTool.oauth,
|
||||
params: shareCalendarTool.params,
|
||||
request: shareCalendarTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiAclRule = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
role: data.role,
|
||||
scope: data.scope,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'ACL rule ID' },
|
||||
role: { type: 'string', description: 'Granted access role' },
|
||||
scope: { type: 'json', description: 'Grantee scope (type and value)' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3'
|
||||
|
||||
export interface CalendarAttendee {
|
||||
id?: string
|
||||
email: string
|
||||
displayName?: string
|
||||
organizer?: boolean
|
||||
self?: boolean
|
||||
resource?: boolean
|
||||
optional?: boolean
|
||||
responseStatus: string
|
||||
comment?: string
|
||||
additionalGuests?: number
|
||||
}
|
||||
|
||||
interface BaseGoogleCalendarParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
}
|
||||
|
||||
export interface GoogleCalendarCreateParams extends BaseGoogleCalendarParams {
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
startDateTime: string
|
||||
endDateTime: string
|
||||
timeZone?: string
|
||||
attendees?: string[]
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
recurrence?: string | string[]
|
||||
addGoogleMeet?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarListParams extends BaseGoogleCalendarParams {
|
||||
timeMin?: string
|
||||
timeMax?: string
|
||||
q?: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
singleEvents?: boolean
|
||||
orderBy?: 'startTime' | 'updated'
|
||||
showDeleted?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarGetParams extends BaseGoogleCalendarParams {
|
||||
eventId: string
|
||||
}
|
||||
|
||||
export interface GoogleCalendarUpdateParams extends BaseGoogleCalendarParams {
|
||||
eventId: string
|
||||
summary?: string
|
||||
description?: string
|
||||
location?: string
|
||||
startDateTime?: string
|
||||
endDateTime?: string
|
||||
timeZone?: string
|
||||
attendees?: string[]
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
recurrence?: string | string[]
|
||||
addGoogleMeet?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarDeleteParams extends BaseGoogleCalendarParams {
|
||||
eventId: string
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
}
|
||||
|
||||
export interface GoogleCalendarQuickAddParams extends BaseGoogleCalendarParams {
|
||||
text: string
|
||||
attendees?: string[]
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
}
|
||||
|
||||
export interface GoogleCalendarInviteParams extends BaseGoogleCalendarParams {
|
||||
eventId: string
|
||||
attendees: string[]
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
replaceExisting?: boolean
|
||||
}
|
||||
|
||||
interface GoogleCalendarMoveParams extends BaseGoogleCalendarParams {
|
||||
eventId: string
|
||||
destinationCalendarId: string
|
||||
sendUpdates?: 'all' | 'externalOnly' | 'none'
|
||||
}
|
||||
|
||||
interface GoogleCalendarInstancesParams extends BaseGoogleCalendarParams {
|
||||
eventId: string
|
||||
timeMin?: string
|
||||
timeMax?: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
showDeleted?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarFreeBusyParams {
|
||||
accessToken: string
|
||||
calendarIds: string
|
||||
timeMin: string
|
||||
timeMax: string
|
||||
timeZone?: string
|
||||
}
|
||||
|
||||
interface GoogleCalendarListCalendarsParams {
|
||||
accessToken: string
|
||||
minAccessRole?: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
showDeleted?: boolean
|
||||
showHidden?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarCreateCalendarParams {
|
||||
accessToken: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone?: string
|
||||
}
|
||||
|
||||
export type GoogleCalendarAclRole = 'freeBusyReader' | 'reader' | 'writer' | 'owner'
|
||||
type GoogleCalendarAclScopeType = 'user' | 'group' | 'domain' | 'default'
|
||||
|
||||
export interface GoogleCalendarShareCalendarParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
role: GoogleCalendarAclRole
|
||||
scopeType: GoogleCalendarAclScopeType
|
||||
scopeValue?: string
|
||||
sendNotifications?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarListAclParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
showDeleted?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarUnshareCalendarParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
ruleId: string
|
||||
}
|
||||
|
||||
export type GoogleCalendarToolParams =
|
||||
| GoogleCalendarCreateParams
|
||||
| GoogleCalendarListParams
|
||||
| GoogleCalendarGetParams
|
||||
| GoogleCalendarUpdateParams
|
||||
| GoogleCalendarDeleteParams
|
||||
| GoogleCalendarQuickAddParams
|
||||
| GoogleCalendarInviteParams
|
||||
| GoogleCalendarMoveParams
|
||||
| GoogleCalendarInstancesParams
|
||||
| GoogleCalendarFreeBusyParams
|
||||
| GoogleCalendarListCalendarsParams
|
||||
| GoogleCalendarCreateCalendarParams
|
||||
| GoogleCalendarShareCalendarParams
|
||||
| GoogleCalendarListAclParams
|
||||
| GoogleCalendarUnshareCalendarParams
|
||||
|
||||
interface EventMetadata {
|
||||
id: string
|
||||
htmlLink: string
|
||||
hangoutLink?: string
|
||||
status: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
recurrence?: string[]
|
||||
start: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
end: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
attendees?: CalendarAttendee[]
|
||||
creator?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
organizer?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface ListMetadata {
|
||||
nextPageToken?: string
|
||||
nextSyncToken?: string
|
||||
events: EventMetadata[]
|
||||
timeZone: string
|
||||
}
|
||||
|
||||
interface GoogleCalendarToolResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata | ListMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarCreateResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarListResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: ListMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarGetResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarQuickAddResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarUpdateResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarInviteResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata
|
||||
}
|
||||
}
|
||||
|
||||
interface GoogleCalendarEvent {
|
||||
id: string
|
||||
status: string
|
||||
htmlLink: string
|
||||
created: string
|
||||
updated: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
start: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
end: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
attendees?: CalendarAttendee[]
|
||||
creator?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
organizer?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
reminders?: {
|
||||
useDefault: boolean
|
||||
overrides?: Array<{
|
||||
method: string
|
||||
minutes: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
interface GoogleCalendarEventDateTime {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
|
||||
interface GoogleCalendarConferenceCreateRequest {
|
||||
createRequest: {
|
||||
requestId: string
|
||||
conferenceSolutionKey: { type: string }
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarEventRequestBody {
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
start: GoogleCalendarEventDateTime
|
||||
end: GoogleCalendarEventDateTime
|
||||
attendees?: Array<{
|
||||
email: string
|
||||
}>
|
||||
recurrence?: string[]
|
||||
conferenceData?: GoogleCalendarConferenceCreateRequest
|
||||
}
|
||||
|
||||
export interface GoogleCalendarApiEventResponse {
|
||||
id: string
|
||||
status: string
|
||||
htmlLink: string
|
||||
hangoutLink?: string
|
||||
created?: string
|
||||
updated?: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
recurrence?: string[]
|
||||
recurringEventId?: string
|
||||
start: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
end: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
attendees?: CalendarAttendee[]
|
||||
creator?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
organizer?: {
|
||||
email: string
|
||||
displayName?: string
|
||||
}
|
||||
conferenceData?: Record<string, unknown>
|
||||
reminders?: {
|
||||
useDefault: boolean
|
||||
overrides?: Array<{
|
||||
method: string
|
||||
minutes: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarApiCalendarResponse {
|
||||
kind: string
|
||||
etag: string
|
||||
id: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone?: string
|
||||
}
|
||||
|
||||
export interface GoogleCalendarApiAclRule {
|
||||
kind: string
|
||||
etag: string
|
||||
id: string
|
||||
role: string
|
||||
scope: {
|
||||
type: string
|
||||
value?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarApiAclListResponse {
|
||||
kind: string
|
||||
etag: string
|
||||
nextPageToken?: string
|
||||
items: GoogleCalendarApiAclRule[]
|
||||
}
|
||||
|
||||
export interface GoogleCalendarApiListResponse {
|
||||
kind: string
|
||||
etag: string
|
||||
summary: string
|
||||
description?: string
|
||||
updated: string
|
||||
timeZone: string
|
||||
accessRole: string
|
||||
defaultReminders: Array<{
|
||||
method: string
|
||||
minutes: number
|
||||
}>
|
||||
nextPageToken?: string
|
||||
nextSyncToken?: string
|
||||
items: GoogleCalendarApiEventResponse[]
|
||||
}
|
||||
|
||||
interface GoogleCalendarDeleteResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
eventId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface GoogleCalendarMoveResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: EventMetadata
|
||||
}
|
||||
}
|
||||
|
||||
interface GoogleCalendarInstancesResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
nextPageToken?: string
|
||||
timeZone: string
|
||||
instances: Array<
|
||||
EventMetadata & {
|
||||
recurringEventId: string
|
||||
originalStartTime: {
|
||||
dateTime?: string
|
||||
date?: string
|
||||
timeZone?: string
|
||||
}
|
||||
}
|
||||
>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarFreeBusyResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
timeMin: string
|
||||
timeMax: string
|
||||
calendars: Record<
|
||||
string,
|
||||
{
|
||||
busy: Array<{ start: string; end: string }>
|
||||
errors?: Array<{ domain: string; reason: string }>
|
||||
}
|
||||
>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarApiFreeBusyResponse {
|
||||
kind: string
|
||||
timeMin: string
|
||||
timeMax: string
|
||||
calendars: Record<
|
||||
string,
|
||||
{
|
||||
busy: Array<{ start: string; end: string }>
|
||||
errors?: Array<{ domain: string; reason: string }>
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
interface GoogleCalendarListCalendarsResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
nextPageToken?: string
|
||||
calendars: Array<{
|
||||
id: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone: string
|
||||
accessRole: string
|
||||
backgroundColor: string
|
||||
foregroundColor: string
|
||||
primary?: boolean
|
||||
hidden?: boolean
|
||||
selected?: boolean
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarCreateCalendarResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarShareCalendarResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
role: string
|
||||
scope: { type: string; value?: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarListAclResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
nextPageToken?: string
|
||||
rules: Array<{ id: string; role: string; scope: { type: string; value?: string } }>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleCalendarUnshareCalendarResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
ruleId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type GoogleCalendarResponse =
|
||||
| GoogleCalendarCreateResponse
|
||||
| GoogleCalendarListResponse
|
||||
| GoogleCalendarGetResponse
|
||||
| GoogleCalendarQuickAddResponse
|
||||
| GoogleCalendarInviteResponse
|
||||
| GoogleCalendarUpdateResponse
|
||||
| GoogleCalendarDeleteResponse
|
||||
| GoogleCalendarMoveResponse
|
||||
| GoogleCalendarInstancesResponse
|
||||
| GoogleCalendarFreeBusyResponse
|
||||
| GoogleCalendarListCalendarsResponse
|
||||
| GoogleCalendarCreateCalendarResponse
|
||||
| GoogleCalendarShareCalendarResponse
|
||||
| GoogleCalendarListAclResponse
|
||||
| GoogleCalendarUnshareCalendarResponse
|
||||
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarUnshareCalendarParams,
|
||||
type GoogleCalendarUnshareCalendarResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const buildUnshareUrl = (params: GoogleCalendarUnshareCalendarParams) => {
|
||||
const calendarId = params.calendarId?.trim() || 'primary'
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/acl/${encodeURIComponent(params.ruleId.trim())}`
|
||||
}
|
||||
|
||||
export const unshareCalendarTool: ToolConfig<
|
||||
GoogleCalendarUnshareCalendarParams,
|
||||
GoogleCalendarUnshareCalendarResponse
|
||||
> = {
|
||||
id: 'google_calendar_unshare_calendar',
|
||||
name: 'Google Calendar Remove Sharing',
|
||||
description: 'Revoke an access control rule (sharing) from a calendar',
|
||||
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: 'Calendar ID to modify (e.g., primary or calendar@group.calendar.google.com)',
|
||||
},
|
||||
ruleId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ACL rule ID to remove (e.g., user:person@example.com)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: buildUnshareUrl,
|
||||
method: 'DELETE',
|
||||
headers: (params: GoogleCalendarUnshareCalendarParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (response.status === 204 || response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: 'Sharing rule successfully removed',
|
||||
metadata: {
|
||||
ruleId: params?.ruleId || '',
|
||||
deleted: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = await response.json().catch(() => null)
|
||||
throw new Error(errorData?.error?.message || 'Failed to remove sharing rule')
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Removal confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Removal details including rule ID',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarUnshareCalendarV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
ruleId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const unshareCalendarV2Tool: ToolConfig<
|
||||
GoogleCalendarUnshareCalendarParams,
|
||||
GoogleCalendarUnshareCalendarV2Response
|
||||
> = {
|
||||
id: 'google_calendar_unshare_calendar_v2',
|
||||
name: 'Google Calendar Remove Sharing',
|
||||
description:
|
||||
'Revoke an access control rule (sharing) from a calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: unshareCalendarTool.oauth,
|
||||
params: unshareCalendarTool.params,
|
||||
request: unshareCalendarTool.request,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (response.status === 204 || response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ruleId: params?.ruleId || '',
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const errorData = await response.json().catch(() => null)
|
||||
throw new Error(errorData?.error?.message || 'Failed to remove sharing rule')
|
||||
},
|
||||
outputs: {
|
||||
ruleId: { type: 'string', description: 'Removed ACL rule ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether removal was successful' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type CalendarAttendee,
|
||||
type GoogleCalendarApiEventResponse,
|
||||
type GoogleCalendarEventRequestBody,
|
||||
type GoogleCalendarUpdateParams,
|
||||
type GoogleCalendarUpdateResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import {
|
||||
assertRecurringTimeZone,
|
||||
buildEventDateTime,
|
||||
buildGoogleMeetConferenceData,
|
||||
normalizeAttendees,
|
||||
normalizeRecurrence,
|
||||
} from '@/tools/google_calendar/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
type EventPatchBody = Partial<GoogleCalendarEventRequestBody>
|
||||
|
||||
export const updateTool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarUpdateResponse> = {
|
||||
id: 'google_calendar_update',
|
||||
name: 'Google Calendar Update Event',
|
||||
description: 'Update an existing event in Google Calendar',
|
||||
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)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Calendar event ID to update',
|
||||
},
|
||||
summary: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New event title/summary',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New event description',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New event location',
|
||||
},
|
||||
startDateTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'New start time. Use a datetime with timezone offset (2025-06-03T10:00:00-08:00) or a date (2025-06-03) for an all-day event',
|
||||
},
|
||||
endDateTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'New end time. Use a datetime with timezone offset (2025-06-03T11:00:00-08:00) or a date (2025-06-04) for an all-day event',
|
||||
},
|
||||
timeZone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'IANA time zone (e.g., America/Los_Angeles) applied to the start/end times provided in this update. Provide a new start and/or end time to change the time zone; a time zone on its own is not applied. Required for recurring events to expand the recurrence correctly.',
|
||||
},
|
||||
attendees: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of attendee email addresses. When one or more emails are provided, they replace the existing attendee list. Leaving this empty keeps the current attendees unchanged (it does not clear them).',
|
||||
},
|
||||
recurrence: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
"Recurrence rule(s) in RFC 5545 format (e.g., RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR). Separate multiple rules with newlines. When provided, replaces the event's recurrence; leaving it empty keeps the existing recurrence unchanged. Requires a timeZone for timed events.",
|
||||
},
|
||||
addGoogleMeet: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attach a Google Meet video conference link to the event',
|
||||
},
|
||||
sendUpdates: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'How to send updates to attendees: all, externalOnly, or none',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleCalendarUpdateParams) => {
|
||||
const calendarId = params.calendarId || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sendUpdates !== undefined) {
|
||||
queryParams.append('sendUpdates', params.sendUpdates)
|
||||
}
|
||||
if (params.addGoogleMeet) {
|
||||
queryParams.append('conferenceDataVersion', '1')
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params: GoogleCalendarUpdateParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleCalendarUpdateParams): EventPatchBody => {
|
||||
const updateData: EventPatchBody = {}
|
||||
const recurrence = normalizeRecurrence(params.recurrence)
|
||||
const isRecurring = recurrence.length > 0
|
||||
|
||||
if (isRecurring) {
|
||||
assertRecurringTimeZone([params.startDateTime, params.endDateTime], params.timeZone)
|
||||
}
|
||||
|
||||
if (params.summary !== undefined) {
|
||||
updateData.summary = params.summary
|
||||
}
|
||||
|
||||
if (params.description !== undefined) {
|
||||
updateData.description = params.description
|
||||
}
|
||||
|
||||
if (params.location !== undefined) {
|
||||
updateData.location = params.location
|
||||
}
|
||||
|
||||
if (params.startDateTime !== undefined) {
|
||||
updateData.start = buildEventDateTime(params.startDateTime, params.timeZone)
|
||||
}
|
||||
|
||||
if (params.endDateTime !== undefined) {
|
||||
updateData.end = buildEventDateTime(params.endDateTime, params.timeZone)
|
||||
}
|
||||
|
||||
const attendees = normalizeAttendees(params.attendees)
|
||||
if (attendees.length > 0) {
|
||||
updateData.attendees = attendees
|
||||
}
|
||||
|
||||
if (isRecurring) {
|
||||
updateData.recurrence = recurrence
|
||||
}
|
||||
|
||||
if (params.addGoogleMeet) {
|
||||
updateData.conferenceData = buildGoogleMeetConferenceData()
|
||||
}
|
||||
|
||||
return updateData
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Event "${data.summary}" updated successfully`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
hangoutLink: data.hangoutLink,
|
||||
status: data.status,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
recurrence: data.recurrence,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees,
|
||||
creator: data.creator,
|
||||
organizer: data.organizer,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Event update confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Updated event metadata including ID, status, Meet link, and details',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarUpdateV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
htmlLink: string
|
||||
hangoutLink: string | null
|
||||
status: string
|
||||
summary: string | null
|
||||
description: string | null
|
||||
location: string | null
|
||||
recurrence: string[] | null
|
||||
start: GoogleCalendarApiEventResponse['start']
|
||||
end: GoogleCalendarApiEventResponse['end']
|
||||
attendees: CalendarAttendee[] | null
|
||||
creator: GoogleCalendarApiEventResponse['creator'] | null
|
||||
organizer: GoogleCalendarApiEventResponse['organizer'] | null
|
||||
}
|
||||
}
|
||||
|
||||
export const updateV2Tool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarUpdateV2Response> =
|
||||
{
|
||||
id: 'google_calendar_update_v2',
|
||||
name: 'Google Calendar Update Event',
|
||||
description: 'Update an existing event in Google Calendar. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: updateTool.oauth,
|
||||
params: updateTool.params,
|
||||
request: updateTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiEventResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
htmlLink: data.htmlLink,
|
||||
hangoutLink: data.hangoutLink ?? null,
|
||||
status: data.status,
|
||||
summary: data.summary ?? null,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
recurrence: data.recurrence ?? null,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
attendees: data.attendees ?? null,
|
||||
creator: data.creator ?? null,
|
||||
organizer: data.organizer ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Event ID' },
|
||||
htmlLink: { type: 'string', description: 'Event link' },
|
||||
hangoutLink: { type: 'string', description: 'Google Meet link', optional: true },
|
||||
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 },
|
||||
recurrence: { type: 'json', description: 'Recurrence rules', 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', optional: true },
|
||||
organizer: { type: 'json', description: 'Event organizer', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarAclRole,
|
||||
type GoogleCalendarApiAclRule,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface GoogleCalendarUpdateAclParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
ruleId: string
|
||||
role: GoogleCalendarAclRole
|
||||
sendNotifications?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleCalendarUpdateAclResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
role: string
|
||||
scope: { type: string; value?: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buildUpdateAclUrl = (params: GoogleCalendarUpdateAclParams) => {
|
||||
const calendarId = params.calendarId?.trim() || 'primary'
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.sendNotifications !== undefined) {
|
||||
queryParams.append('sendNotifications', String(params.sendNotifications))
|
||||
}
|
||||
const queryString = queryParams.toString()
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/acl/${encodeURIComponent(params.ruleId.trim())}${queryString ? `?${queryString}` : ''}`
|
||||
}
|
||||
|
||||
export const updateAclTool: ToolConfig<
|
||||
GoogleCalendarUpdateAclParams,
|
||||
GoogleCalendarUpdateAclResponse
|
||||
> = {
|
||||
id: 'google_calendar_update_acl',
|
||||
name: 'Google Calendar Update Sharing',
|
||||
description: 'Change the access role granted by an existing calendar sharing (ACL) rule',
|
||||
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: 'Calendar ID to modify (e.g., primary or calendar@group.calendar.google.com)',
|
||||
},
|
||||
ruleId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ACL rule ID to update (e.g., user:person@example.com)',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New access role to grant: freeBusyReader, reader, writer, or owner',
|
||||
},
|
||||
sendNotifications: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Whether to send a notification email about the change. Defaults to true.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: buildUpdateAclUrl,
|
||||
method: 'PATCH',
|
||||
headers: (params: GoogleCalendarUpdateAclParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleCalendarUpdateAclParams) => ({ role: params.role }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiAclRule = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Updated sharing rule for ${data.scope?.value || data.scope?.type} to ${data.role}`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
role: data.role,
|
||||
scope: data.scope,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Sharing update confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Updated ACL rule (id, role, scope)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarUpdateAclV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
role: string
|
||||
scope: { type: string; value?: string }
|
||||
}
|
||||
}
|
||||
|
||||
export const updateAclV2Tool: ToolConfig<
|
||||
GoogleCalendarUpdateAclParams,
|
||||
GoogleCalendarUpdateAclV2Response
|
||||
> = {
|
||||
id: 'google_calendar_update_acl_v2',
|
||||
name: 'Google Calendar Update Sharing',
|
||||
description:
|
||||
'Change the access role granted by an existing calendar sharing (ACL) rule. Returns API-aligned fields only.',
|
||||
version: '2.0.0',
|
||||
oauth: updateAclTool.oauth,
|
||||
params: updateAclTool.params,
|
||||
request: updateAclTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiAclRule = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
role: data.role,
|
||||
scope: data.scope,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'ACL rule ID' },
|
||||
role: { type: 'string', description: 'Granted access role' },
|
||||
scope: { type: 'json', description: 'Grantee scope (type and value)' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
CALENDAR_API_BASE,
|
||||
type GoogleCalendarApiCalendarResponse,
|
||||
} from '@/tools/google_calendar/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface GoogleCalendarUpdateCalendarParams {
|
||||
accessToken: string
|
||||
calendarId?: string
|
||||
summary?: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone?: string
|
||||
}
|
||||
|
||||
export interface GoogleCalendarUpdateCalendarResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
summary: string
|
||||
description?: string
|
||||
location?: string
|
||||
timeZone?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buildUpdateCalendarUrl = (params: GoogleCalendarUpdateCalendarParams) => {
|
||||
const calendarId = params.calendarId?.trim() || 'primary'
|
||||
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}`
|
||||
}
|
||||
|
||||
const buildUpdateCalendarBody = (params: GoogleCalendarUpdateCalendarParams) => {
|
||||
const body: Record<string, string> = {}
|
||||
if (params.summary !== undefined) body.summary = params.summary
|
||||
if (params.description !== undefined) body.description = params.description
|
||||
if (params.location !== undefined) body.location = params.location
|
||||
if (params.timeZone !== undefined) body.timeZone = params.timeZone
|
||||
return body
|
||||
}
|
||||
|
||||
export const updateCalendarTool: ToolConfig<
|
||||
GoogleCalendarUpdateCalendarParams,
|
||||
GoogleCalendarUpdateCalendarResponse
|
||||
> = {
|
||||
id: 'google_calendar_update_calendar',
|
||||
name: 'Google Calendar Update Calendar',
|
||||
description: "Update a secondary calendar's metadata (title, description, location, time zone)",
|
||||
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: 'Calendar ID to update (e.g., primary or calendar@group.calendar.google.com)',
|
||||
},
|
||||
summary: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New title for the calendar',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New description for the calendar',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New geographic location of the calendar as free-form text',
|
||||
},
|
||||
timeZone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New time zone of the calendar as an IANA name (e.g., America/Los_Angeles)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: buildUpdateCalendarUrl,
|
||||
method: 'PATCH',
|
||||
headers: (params: GoogleCalendarUpdateCalendarParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: buildUpdateCalendarBody,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiCalendarResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: `Calendar "${data.summary}" updated successfully`,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
summary: data.summary,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
timeZone: data.timeZone,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Calendar update confirmation message' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Updated calendar metadata (id, summary, description, location, timeZone)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface GoogleCalendarUpdateCalendarV2Response {
|
||||
success: boolean
|
||||
output: {
|
||||
id: string
|
||||
summary: string
|
||||
description: string | null
|
||||
location: string | null
|
||||
timeZone: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const updateCalendarV2Tool: ToolConfig<
|
||||
GoogleCalendarUpdateCalendarParams,
|
||||
GoogleCalendarUpdateCalendarV2Response
|
||||
> = {
|
||||
id: 'google_calendar_update_calendar_v2',
|
||||
name: 'Google Calendar Update Calendar',
|
||||
description: "Update a secondary calendar's metadata. Returns API-aligned fields only.",
|
||||
version: '2.0.0',
|
||||
oauth: updateCalendarTool.oauth,
|
||||
params: updateCalendarTool.params,
|
||||
request: updateCalendarTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: GoogleCalendarApiCalendarResponse = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
summary: data.summary,
|
||||
description: data.description ?? null,
|
||||
location: data.location ?? null,
|
||||
timeZone: data.timeZone ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Calendar ID' },
|
||||
summary: { type: 'string', description: 'Calendar title' },
|
||||
description: { type: 'string', description: 'Calendar description', optional: true },
|
||||
location: { type: 'string', description: 'Calendar location', optional: true },
|
||||
timeZone: { type: 'string', description: 'Calendar time zone', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import type { GoogleCalendarEventRequestBody } from '@/tools/google_calendar/types'
|
||||
|
||||
type EventDateTime = GoogleCalendarEventRequestBody['start']
|
||||
|
||||
const DEFAULT_TIME_ZONE = 'America/Los_Angeles'
|
||||
const TZ_OFFSET_PATTERN = /([+-]\d{2}:?\d{2}|Z)$/
|
||||
|
||||
/**
|
||||
* Build a Google Calendar event date/time object from a user-supplied value.
|
||||
*
|
||||
* A date-only value (e.g. `2025-06-03`) produces an all-day `{ date }` object.
|
||||
* A datetime value produces `{ dateTime, timeZone? }`. An explicitly provided
|
||||
* timezone always wins. Otherwise a default zone is attached only for "naive"
|
||||
* datetimes that carry no UTC offset — when an offset is present it is authoritative
|
||||
* and is never overridden with a guessed zone, which would misalign the time.
|
||||
*
|
||||
* For recurring events the Calendar API requires a named `timeZone` on start/end;
|
||||
* callers should pass the user's timezone explicitly (an RFC3339 offset alone is
|
||||
* insufficient to expand a recurrence across DST).
|
||||
*/
|
||||
export function buildEventDateTime(value: string, timeZone: string | undefined): EventDateTime {
|
||||
const isDateOnly = !value.includes('T')
|
||||
if (isDateOnly) {
|
||||
return { date: value }
|
||||
}
|
||||
|
||||
const hasOffset = TZ_OFFSET_PATTERN.test(value)
|
||||
const result: EventDateTime = { dateTime: value }
|
||||
if (timeZone) {
|
||||
result.timeZone = timeZone
|
||||
} else if (!hasOffset) {
|
||||
result.timeZone = DEFAULT_TIME_ZONE
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Normalize a comma/newline-separated string or array of attendee emails into `[{ email }]`. */
|
||||
export function normalizeAttendees(
|
||||
attendees: string | string[] | undefined
|
||||
): Array<{ email: string }> {
|
||||
if (!attendees) return []
|
||||
|
||||
const list = Array.isArray(attendees)
|
||||
? attendees
|
||||
: attendees.split(',').map((email) => email.trim())
|
||||
|
||||
return list.filter((email) => email.length > 0).map((email) => ({ email }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurring events require a named `timeZone` on their timed start/end — the Calendar API
|
||||
* rejects them otherwise, and an RFC3339 offset is not a substitute (an IANA zone cannot be
|
||||
* derived from a fixed offset). Throws a clear error so we fail fast with guidance instead of
|
||||
* silently guessing a zone (which would misalign the recurrence) or sending an invalid request.
|
||||
* All-day recurring events (date-only values) do not need a timezone and are allowed.
|
||||
*/
|
||||
export function assertRecurringTimeZone(
|
||||
dateTimes: Array<string | undefined>,
|
||||
timeZone: string | undefined
|
||||
): void {
|
||||
if (timeZone) return
|
||||
const hasTimedValue = dateTimes.some((value) => value?.includes('T'))
|
||||
if (hasTimedValue) {
|
||||
throw new Error(
|
||||
'Recurring events require a time zone. Provide the timeZone parameter (an IANA name, e.g. America/New_York).'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Normalize recurrence rules (single string, newline-separated string, or array) into an array. */
|
||||
export function normalizeRecurrence(recurrence: string | string[] | undefined): string[] {
|
||||
if (!recurrence) return []
|
||||
|
||||
const list = Array.isArray(recurrence) ? recurrence : recurrence.split('\n')
|
||||
|
||||
return list.map((rule) => rule.trim()).filter((rule) => rule.length > 0)
|
||||
}
|
||||
|
||||
/** Build a `conferenceData.createRequest` payload that asks Google to attach a Meet link. */
|
||||
export function buildGoogleMeetConferenceData(): GoogleCalendarEventRequestBody['conferenceData'] {
|
||||
return {
|
||||
createRequest: {
|
||||
requestId: generateId(),
|
||||
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user