@framers/agentos-ext-calendar-google

中文文档
1.0.6发布于 2026/11/11Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 2.2万

Google Calendar for AgentOS: event tools through googleapis, and a typed read surface on fetch for a host that keeps its own grant

原始使用文档
文档目录15 节
  1. Installation
  2. Usage
  3. Configuration
  4. Tools
  5. calendarListEvents
  6. calendarCreateEvent
  7. calendarUpdateEvent
  8. calendarDeleteEvent
  9. calendarFindFreeTime
  10. calendarListCalendars
  11. Reading with your own grant
  12. The grant
  13. Notes
  14. Releases
  15. License

Google Calendar

This pack gives an agent event listing, creation, updates, deletion, calendar discovery and free time queries through the Google Calendar API using googleapis and OAuth2. On its ./reader subpath it gives a host that keeps its own grant a typed read surface on fetch: the windowed read, the incremental read, the read by id, the event's shape and the meeting link's candidates. The same subpath carries the flow that makes that grant, GoogleCalendarOAuthFlow: one read-only events scope with openid and email, held exactly at the exchange and at every refresh.

Installation

npm install @framers/agentos-ext-calendar-google @framers/agentos @framers/agentos-ext-account-sources

Peer dependencies: @framers/agentos and @framers/agentos-ext-account-sources, each at or above the floor this package's peerDependencies names; npm view @framers/agentos-ext-calendar-google peerDependencies prints the latest release's. The grant on ./reader runs on the Drive pack's flow (@framers/agentos-ext-google-drive, a dependency of this pack), which loads both, so ./reader needs both installed, even in a host that uses its reads alone. The package is an ES module: import it; a CommonJS require() of it is not supported.

Usage

import { AgentOS } from '@framers/agentos';

const agentos = await AgentOS.create({
  extensionManifest: {
    packs: [
      {
        package: '@framers/agentos-ext-calendar-google',
        options: {
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
          refreshToken: process.env.GOOGLE_REFRESH_TOKEN || process.env.GOOGLE_CALENDAR_REFRESH_TOKEN,
        },
      },
    ],
  },
});

Configuration

OptionSecret idEnvironment variableRequiredDefault
clientIdgoogle.clientIdGOOGLE_CLIENT_IDYesNo default
clientSecretgoogle.clientSecretGOOGLE_CLIENT_SECRETYesNo default
refreshTokengoogle.refreshTokenGOOGLE_REFRESH_TOKEN, then GOOGLE_CALENDAR_REFRESH_TOKENYesNo default
accessTokenNoNo token
priorityNo50
secretsNoNo map
Context loggerNoNo logger

Nonempty credential options precede options.secrets, followed by environment fallbacks in the order shown. accessToken is read directly from options. The logger receives activation and deactivation messages.

Tools

ToolWhat it does
calendarListEventsLists events in a time range with summaries, times, locations and attendees.
calendarCreateEventCreates an event with times, description, location and attendees.
calendarUpdateEventUpdates the supplied fields of an existing event.
calendarDeleteEventDeletes an event by ID.
calendarFindFreeTimeQueries calendar availability and returns open slots meeting the requested duration.
calendarListCalendarsLists accessible calendars with IDs, names and time zones.

calendarListEvents

ParameterTypeRequiredDescription
calendarIdstringNoCalendar ID to query (defaults to "primary")
timeMinstringNoStart of time range in ISO 8601 format (e.g. "2025-01-15T09:00:00Z")
timeMaxstringNoEnd of time range in ISO 8601 format (e.g. "2025-01-15T17:00:00Z")
maxResultsnumberNoMaximum number of events to return (default 10, max 250)

calendarCreateEvent

ParameterTypeRequiredDescription
calendarIdstringNoCalendar ID to create the event in (defaults to "primary")
summarystringYesEvent title/summary
startstringYesStart datetime in ISO 8601 format (e.g. "2025-01-15T09:00:00-05:00")
endstringYesEnd datetime in ISO 8601 format (e.g. "2025-01-15T10:00:00-05:00")
descriptionstringNoEvent description or notes
locationstringNoEvent location (address or place name)
attendeesarray of stringsNoArray of attendee email addresses
timeZonestringNoIANA time zone (e.g. "America/New_York"). Applied to start/end if not embedded.

calendarUpdateEvent

ParameterTypeRequiredDescription
calendarIdstringNoCalendar ID containing the event (defaults to "primary")
eventIdstringYesID of the event to update
summarystringNoUpdated event title/summary
startstringNoUpdated start datetime in ISO 8601 format
endstringNoUpdated end datetime in ISO 8601 format
descriptionstringNoUpdated event description
locationstringNoUpdated event location
timeZonestringNoIANA time zone for start/end (e.g. "America/New_York")

calendarDeleteEvent

ParameterTypeRequiredDescription
calendarIdstringNoCalendar ID containing the event (defaults to "primary")
eventIdstringYesID of the event to delete

calendarFindFreeTime

ParameterTypeRequiredDescription
calendarIdsarray of stringsNoCalendar IDs to check (defaults to ["primary"])
timeMinstringYesStart of search window in ISO 8601 format
timeMaxstringYesEnd of search window in ISO 8601 format
durationMinutesnumberYesMinimum duration of free slot in minutes

calendarListCalendars

The input schema has no parameters.

Reading with your own grant

@framers/agentos-ext-calendar-google/reader reads a calendar with an access token the host supplies, through fetch. It loads neither googleapis nor the tools above.

import { GoogleCalendarReader, meetingLinkCandidates } from '@framers/agentos-ext-calendar-google/reader';

const reader = new GoogleCalendarReader({ accessToken: async () => grant.accessToken() });

// The windowed read: every page of the next seven days, and the last page's sync token.
const now = new Date();
const week = await reader.readWindow({
  calendarId: 'primary',
  timeMin: now,
  timeMax: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000),
  pageSize: 250,
  maxPages: 10,
});
for (const event of week.events) {
  console.log(event.id, event.start?.dateTime, meetingLinkCandidates(event));
}

// The incremental read: what changed since the token, whatever its dates; a 410 answers gone.
if (week.complete && week.nextSyncToken !== null) {
  const changes = await reader.readChanges({ calendarId: 'primary', syncToken: week.nextSyncToken, pageSize: 250, maxPages: 10 });
  if (changes.gone) {
    // Google refused the token: clear it and read the window again.
  } else {
    for (const event of changes.events) {
      if (event.status === 'cancelled') console.log('ended', event.id);
    }
  }
}

// One event by its id; a 404 or a 410 answers gone.
const read = await reader.readEvent({ calendarId: 'primary', eventId: 'fixtureevent1' });

Every page of readWindow and readChanges is asked with singleEvents=true, and never with orderBy, timeMin or timeMax beside a sync token; readEvent sends no query, since Google's events.get takes no singleEvents. A read cut at maxPages answers complete: false and no token. onPage runs after each page is read, before the next is asked, so a host can renew a lease there.

mapCalendarEvent(raw) gives the event's shape: the instance's id and its series' (recurringEventId), the status, start and end as an instant with its zone or an all-day date, originalStartTime for a moved or cancelled instance, organizerSelf, the guests with their self, resource and organizer flags and their answer, guestsOmitted, the conference's video entry point with its solution, location, description, summary and updated. A video entry point whose address does not parse as HTTPS with a host reads null. A cancelled instance of a series carries its id, its series' id and its original start and no times; a deleted event its id alone; every other field reads null or empty. A body with no id maps to null.

meetingLinkCandidates(event) answers the addresses the meeting may be joined at, in order: the conference's video entry point alone when it has one; otherwise the Meet and Teams addresses the location names, then those the description names, each once. An address holds printable ASCII alone: it ends at a blank, a control character, a quote, a bracket or any character past ASCII, such as a curly quote, an ideographic comma or an emoji, which neither service writes in its addresses. The stops that close a sentence (., ,, ;, :, ! and ?) are dropped from its end. The location and the description are read as HTML writes them: a character reference reads as the character it names. A character an address holds stays in it, so ?, ? and ? read as ?, and &, & and & as &. A reference to a blank, a control character, a quote or a bracket, such as  , " or <, ends an address as that character would, and so do a named reference to any other character and a number that names no character; an address written right after such a reference is read as the next. hangoutLink is never read. A host keeps the first candidate its own bot provider accepts.

A failure is a CalendarReadFault whose reason is a fixed word, with Google's text never carried; its message is calendar_read_<reason>:

ReasonWhen
refusedA 401, or a 403 that names no rate or quota
limitedA 429, or a 403 naming a rate or quota (rateLimitExceeded, userRateLimitExceeded, quotaExceeded, dailyLimitExceeded)
failedAny other answer, a network failure, a body that is not JSON, or a malformed page
timeoutA call past deadlineMs (10,000 by default), from the wait for its token to the last byte of its answer
too_largeAn answer past maxBytes (8,388,608 by default)
cancelledThe caller's signal aborted, before the call or while it waited on its token, on Google's answer or on its body
configurationA malformed input, bound or address

The deadline and the signal hold whether or not the token supplier, fetchImpl or the answer's body heeds them: a token that comes after a stop is never sent, and an answer that comes after one is let go unread. An error the accessToken supplier throws is the host's own, such as a refused grant or a refresh another process holds, and passes through as thrown, never as a CalendarReadFault.

readChanges answers { gone: true } for a 410, and readEvent for a 404 or a 410; neither throws for those.

The api option names a stand-in for tests. It is taken only on CI (CI=true) at a loopback or .invalid host, with no userinfo, query or fragment, over HTTPS or over plain HTTP at a loopback host; any other address throws configuration.

The grant

GoogleCalendarOAuthFlow, on the same subpath, is Google's grant for the person's calendar: the Drive pack's GoogleOAuthFlow (@framers/agentos-ext-google-drive, a dependency of this pack) under the calendar's rule, calendarGrant. It asks one read-only events scope, https://www.googleapis.com/auth/calendar.events.owned.readonly ("See the events on Google calendars you own.") by default or https://www.googleapis.com/auth/calendar.events.readonly ("View events on all your calendars.") as the scope option, with openid and email, offline and with consent, without incremental scopes. A grant counts only when its scopes are exactly that set, at the exchange and at every refresh.

import { GoogleCalendarOAuthFlow, GoogleCalendarReader } from '@framers/agentos-ext-calendar-google/reader';

const callback = 'https://app.example.com/calendar/google/callback';

// The request that sends the person to Google: keep the state and the verifier until they come back.
const begun = new GoogleCalendarOAuthFlow({ clientId, clientSecret }, null).beginCalendar(callback);

// The request Google sends the person back to. `store` is an IOAuthTokenStore, such as the auth module's
// SealedTokenStore under the connection's namespace.
const flow = new GoogleCalendarOAuthFlow({ clientId, clientSecret }, store);
const tokens = await flow.complete({ code, state, expectedState: begun.state, codeVerifier: begun.codeVerifier, redirectUri: callback });
const address = tokens.metadata?.email; // the address the grant was made for, read from the ID token; the store never sees it
await flow.keep(connectionId, tokens);

// Later: an access token for the reader, refreshed one at a time a key; and the grant's end.
const reader = new GoogleCalendarReader({ accessToken: () => flow.accessToken(connectionId) });
await flow.forget(connectionId); // revokes the kept refresh token at Google's revocation address and clears the grant

complete answers the grant with metadata.email, the address the ID token of the token endpoint's answer names, lower-cased. idTokenAccount(idToken, clientId, now) reads that token's claims and refuses, as GoogleOAuthFault invalid_response, a token whose issuer is not Google's, whose audience is not the client alone, whose authorized party (azp), when present, is another client, whose expiry is not a finite number past now, whose address is unverified or absent, or which is missing or malformed (a second audience and another party are refused by OpenID Connect Core 1.0, section 3.1.3.7, items 3 and 5); the signature is not checked, since a token received from the token endpoint itself is validated by the connection's TLS (OpenID Connect Core 1.0, section 3.1.3.7, item 6). keep hands the store the refresh token and the scopes alone, never the address, the access token or the ID token.

A grant missing a scope, holding an extra one or lacking a refresh token is refused at the exchange (GoogleOAuthFault scope or refresh_token); a refresh whose answer names scopes outside the set throws scope, and one that names none keeps the kept set. revoke(token) posts to Google's revocation address and resolves when Google revokes the token or answers that it is already expired or revoked; any other refusal is the auth module's OAuthGrantRefused. The flow's options are the Drive pack's (fetchImpl, signal and, on CI alone, stand-in endpoints) with scope in place of grant; its faults are the Drive pack's GoogleOAuthFault, exported from this subpath too, so a host that installs this pack alone catches them from here. Options that are not an object, such as a fetch given in their place, and a scope other than the two throw configuration.

Notes

The package requires Node.js >=22. Missing required OAuth credentials make the factory throw, even when accessToken is provided. Activation initializes the OAuth client; service operations require initialization. The pack also registers googleCalendarService with kind productivity.

Event creation, updates and deletion change the selected calendar. Creation and updates send start and end as dateTime fields. The update tool applies timeZone only alongside a supplied start or end. Attendees are accepted by the create tool; the update tool has no attendee parameter.

Event listing defaults to primary, orders events by start time and caps maxResults at 250. Free time search defaults absent or empty calendarIds to primary, merges overlapping busy intervals and returns entire gaps meeting the minimum duration. Tool failures return success: false and an error message.

Releases

New versions are published from framerslab/agentos-extensions by GitHub Actions through npm trusted publishing, with a provenance statement.

License

Apache-2.0

版本与发布记录

@framers/agentos-ext-calendar-google 共发布 14 个版本,首次发布于 2026/02/08;过去 12 个月发布了 14 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.0.6latest2026/11/114 天前
1.0.52026/11/0114 天前
1.0.42026/10/3115 天前
1.0.32026/10/1927 天前
1.0.22026/10/1828 天前
1.0.12026/10/1829 天前

关于 @framers/agentos-ext-calendar-google 的常见问题

@framers/agentos-ext-calendar-google 是什么?

Google Calendar for AgentOS: event tools through googleapis, and a typed read surface on fetch for a host that keeps its own grant

如何安装 @framers/agentos-ext-calendar-google?

在项目目录运行 npm install @framers/agentos-ext-calendar-google,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@framers/agentos(>=0.13.144)、@framers/agentos-ext-account-sources(>=0.10.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@framers/agentos-ext-calendar-google 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@framers/agentos-ext-calendar-google 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@framers/agentos-ext-calendar-google 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@framers/agentos-ext-calendar-google 可以商用吗?

@framers/agentos-ext-calendar-google 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@framers/agentos-ext-calendar-google 的下载量有多少?

根据 npm 官方下载统计,@framers/agentos-ext-calendar-google 在 2026/10/16 – 2026/11/14 这 30 天内被下载 22,338 次,日均约 745 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@framers/agentos-ext-calendar-google 还在维护吗?

最新版本 1.0.6 发布于 2026/11/11(4 天前);至今共发布 14 个版本,其中过去 12 个月发布了 14 个;首次发布于 2026/02/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。