← Public packages
@kody/google
Call Gmail, Calendar, Tasks, Drive, Docs, Sheets, People, YouTube, and Analytics through saved Google OAuth.
src/calendar-across.ts
160 lines · 4.5 KB · TypeScriptimport type {
CalendarEventsAcrossCalendarsParams,
CalendarEventsParams,
CalendarListParams,
} from './calendar.ts'
import type { JsonObject } from './types.ts'
/** Default wall clock for a cross-calendar scan. Under the ~32s MCP client window. */
export const listEventsAcrossCalendarsDefaultBudgetMs = 20_000
/** Hard cap so an explicit budget cannot run unbounded. */
export const listEventsAcrossCalendarsMaxBudgetMs = 60_000
export type CalendarEventsAcrossCalendarsResult = {
items: JsonObject[]
calendars: Array<{ id: string; summary: string }>
failures: string[]
budgetMs: number
truncated: boolean
truncatedReason: 'budget' | null
calendarsScanned: number
}
/**
* Missing, non-finite, and non-positive budgets use the default.
* Larger values are capped. There is no unlimited scan.
*/
export function resolveListEventsAcrossCalendarsBudgetMs(
budgetMs: number | undefined,
) {
if (budgetMs == null || !Number.isFinite(budgetMs) || budgetMs < 1) {
return listEventsAcrossCalendarsDefaultBudgetMs
}
return Math.min(Math.floor(budgetMs), listEventsAcrossCalendarsMaxBudgetMs)
}
export async function scanEventsAcrossCalendars(input: {
params: CalendarEventsAcrossCalendarsParams
now?: () => number
listCalendars: (params: CalendarListParams) => Promise<JsonObject>
listEvents: (params: CalendarEventsParams) => Promise<JsonObject>
log?: (message: string) => void
}): Promise<CalendarEventsAcrossCalendarsResult> {
const now = input.now ?? Date.now
const log = input.log ?? (() => undefined)
const { calendarMaxResults = 250, showHiddenCalendars = true } = input.params
const auth = {
integration: input.params.integration,
integrationName: input.params.integrationName,
account: input.params.account,
}
const budgetMs = resolveListEventsAcrossCalendarsBudgetMs(input.params.budgetMs)
const started = now()
const deadline = started + budgetMs
const calendars: Array<{ id: string; summary: string }> = []
const failures: string[] = []
const items: JsonObject[] = []
let truncated = false
let calendarsScanned = 0
const expired = () => now() >= deadline
log(
`listEventsAcrossCalendars: start budgetMs=${budgetMs} showHidden=${showHiddenCalendars}`,
)
let calendarPageToken: string | undefined
try {
do {
if (expired()) {
truncated = true
break
}
const result = await input.listCalendars({
...auth,
maxResults: calendarMaxResults,
pageToken: calendarPageToken,
showHidden: showHiddenCalendars,
})
for (const calendar of (result.items || []) as Array<JsonObject>) {
if (!calendar.id) continue
calendars.push({
id: String(calendar.id),
summary: String(calendar.summary || calendar.id),
})
}
calendarPageToken = result.nextPageToken
? String(result.nextPageToken)
: undefined
} while (calendarPageToken)
} catch (error) {
failures.push(error instanceof Error ? error.message : String(error))
}
log(`listEventsAcrossCalendars: listed ${calendars.length} calendars`)
if (!calendars.length && !truncated) {
calendars.push({ id: 'primary', summary: 'Primary' })
}
for (const calendar of calendars) {
if (expired()) {
truncated = true
break
}
calendarsScanned += 1
let eventPageToken: string | undefined
try {
do {
if (expired()) {
truncated = true
break
}
const result = await input.listEvents({
...auth,
calendarId: calendar.id,
timeMin: input.params.timeMin,
timeMax: input.params.timeMax,
maxResults: input.params.maxResults ?? 250,
q: input.params.q,
singleEvents: input.params.singleEvents,
orderBy: input.params.orderBy,
fields: input.params.fields,
pageToken: eventPageToken,
})
for (const event of (result.items || []) as Array<JsonObject>) {
items.push({
...event,
calendarId: calendar.id,
calendarSummary: calendar.summary,
})
}
eventPageToken = result.nextPageToken
? String(result.nextPageToken)
: undefined
} while (eventPageToken)
} catch (error) {
failures.push(
`${calendar.summary}: ${error instanceof Error ? error.message : String(error)}`,
)
}
log(
`listEventsAcrossCalendars: scanned ${calendar.summary} events=${items.length}`,
)
if (truncated) break
}
log(
`listEventsAcrossCalendars: done events=${items.length} calendarsScanned=${calendarsScanned}/${calendars.length} truncated=${truncated} elapsedMs=${now() - started}`,
)
return {
items,
calendars,
failures,
budgetMs,
truncated,
truncatedReason: truncated ? 'budget' : null,
calendarsScanned,
}
}