)
Corsair Calendly 插件为 AI Agent 接入 Calendly 调度能力corsair-dev/calendly【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsaircorsair-dev/calendly是 Corsair 生态中面向 Calendly 的官方插件将 Calendly 的会议调度、事件类型、受邀人Invitee、组织与路由表单等 API 能力封装为可供 AI Agent 安全调用的端点Endpoint与 Webhook。本文以 packages/calendly/README.md 为主体结合 插件源码、客户端实现 与 测试文件 等仓库证据完整梳理插件的安装、54 个端点、API Key 认证、6 类 Webhook 事件及其 HMAC 签名校验、持久化数据模型与错误处理机制帮助你在一款产品里安全、可控地开放 Calendly 调度能力给最终用户。插件是什么Corsair 与 Calendly 的桥接层Corsair 是一个面向多租户场景的集成平台核心理念是 Connect your users to their apps——把用户自己的 SaaS 账号连接到你的 AI Agent让 Agent 以用户的身份安全操作其第三方应用。Calendly 插件正是这一理念在日程调度领域的落地以插件形式封装 Calendly 全部核心资源事件类型Event Types、已排定事件Scheduled Events、受邀人Invitees、组织Organizations、群组Groups、路由表单Routing Forms、调度链接Scheduling Links、Webhook 订阅、用户与活动日志。API Key 认证首次使用时Corsair 会向租户tenant索取凭据之后插件用该 Key 代理用户调用 Calendly API。内置 Webhook 处理自动接收并校验 6 类 Calendly Webhook 事件按组织/用户维度路由到对应租户。插件采用 Apache-2.0 许可包名为corsair-dev/calendly当前版本信息见 packages/calendly/package.jsonversion: 0.1.4其 peerDependencies 为corsair 0.1.0与zod ^4.1.13——zod 被用来定义端点入参/出参与 Webhook 载荷的运行时校验。安装在已有 Corsair 应用的仓库中使用 pnpm 添加依赖pnpm add corsair-dev/calendly安装后从包中导入calendly工厂函数并挂载为 Corsair 插件import { calendly } from corsair-dev/calendly; import { corsair } from corsair; const app corsair({ plugins: [ calendly({ // 可选配置见下文 }), ], });从源码结构看插件工厂定义在 packages/calendly/index.ts#L733它接受一个可选的CalendlyPluginOptions对象返回一个满足CorsairPlugincalendly, ...契约的插件实例。仓库内的packages/calendly/tsconfig.json、tsup.config.ts与jest.config.cjs共同支撑其构建tsc --build --force tsup与测试jest流程。端点全景54 个操作与三级风险模型README 的 Endpoints 表格完整列举了插件暴露的操作。每个操作包含三层标识Operation点分隔的端点树路径如eventTypes.create、Operation ID带calendly.api.前缀的全局唯一 ID、Risk风险等级。风险等级由read、write、destructive三档构成这一元数据在 packages/calendly/index.ts#L478 起的calendlyEndpointMeta中逐条声明并满足RequiredPluginEndpointMeta类型约束——也就是说端点树、风险等级与描述在编译期就绑定在一起。下面按资源域分组列出全部 54 个端点与 README 完全一致活动日志Activity LogOperationOperation IDRiskDescriptionactivityLog.listcalendly.api.activityLog.listreadList activity log entries for an organizationactivityLog.listOutgoingCommunicationscalendly.api.activityLog.listOutgoingCommunicationsreadList outgoing communications for an organization事件类型Event TypesOperationOperation IDRiskDescriptioneventTypes.createcalendly.api.eventTypes.createwriteCreate a new event typeeventTypes.createOneOffcalendly.api.eventTypes.createOneOffwriteCreate a one-off event typeeventTypes.getcalendly.api.eventTypes.getreadGet an event type by UUIDeventTypes.listcalendly.api.eventTypes.listreadList all event typeseventTypes.listAvailableTimescalendly.api.eventTypes.listAvailableTimesreadList available times for an event typeeventTypes.listHostscalendly.api.eventTypes.listHostsreadList hosts for an event typeeventTypes.updatecalendly.api.eventTypes.updatewriteUpdate an event typeeventTypes.updateAvailabilitycalendly.api.eventTypes.updateAvailabilitywriteUpdate availability for an event type群组GroupsOperationOperation IDRiskDescriptiongroups.getcalendly.api.groups.getreadGet a group by UUIDgroups.getRelationshipcalendly.api.groups.getRelationshipreadGet a group relationship by UUIDgroups.listcalendly.api.groups.listreadList groups in an organizationgroups.listRelationshipscalendly.api.groups.listRelationshipsreadList relationships for a group受邀人InviteesOperationOperation IDRiskDescriptioninvitees.createcalendly.api.invitees.createwriteCreate an invitee for a one-off event typeinvitees.deleteDatacalendly.api.invitees.deleteDatadestructiveDelete all data for specified invitee emails [DESTRUCTIVE]invitees.deleteNoShowcalendly.api.invitees.deleteNoShowdestructiveDelete an invitee no-show record [DESTRUCTIVE]invitees.getcalendly.api.invitees.getreadGet an event invitee by UUIDinvitees.getNoShowcalendly.api.invitees.getNoShowreadGet an invitee no-show recordinvitees.listcalendly.api.invitees.listreadList invitees for a scheduled eventinvitees.markNoShowcalendly.api.invitees.markNoShowwriteMark an invitee as a no-show组织OrganizationsOperationOperation IDRiskDescriptionorganizations.deleteMembershipcalendly.api.organizations.deleteMembershipdestructiveDelete an organization membership [DESTRUCTIVE]organizations.getcalendly.api.organizations.getreadGet an organization by UUIDorganizations.getInvitationcalendly.api.organizations.getInvitationreadGet an organization invitationorganizations.getMembershipcalendly.api.organizations.getMembershipreadGet an organization membershiporganizations.invitecalendly.api.organizations.invitewriteInvite a user to an organizationorganizations.listInvitationscalendly.api.organizations.listInvitationsreadList organization invitationsorganizations.listMembershipscalendly.api.organizations.listMembershipsreadList organization membershipsorganizations.removeMembercalendly.api.organizations.removeMemberdestructiveRemove a user from the organization [DESTRUCTIVE]organizations.revokeInvitationcalendly.api.organizations.revokeInvitationdestructiveRevoke a users organization invitation [DESTRUCTIVE]路由表单Routing FormsOperationOperation IDRiskDescriptionroutingForms.getcalendly.api.routingForms.getreadGet a routing form by UUIDroutingForms.getSampleWebhookDatacalendly.api.routingForms.getSampleWebhookDatareadGet sample webhook data for an event typeroutingForms.getSubmissioncalendly.api.routingForms.getSubmissionreadGet a routing form submission by UUIDroutingForms.listcalendly.api.routingForms.listreadList routing forms in an organization已排定事件Scheduled EventsOperationOperation IDRiskDescriptionscheduledEvents.cancelcalendly.api.scheduledEvents.canceldestructiveCancel a scheduled event [DESTRUCTIVE]scheduledEvents.deleteDatacalendly.api.scheduledEvents.deleteDatadestructiveDelete all scheduled event data in a time range [DESTRUCTIVE]scheduledEvents.getcalendly.api.scheduledEvents.getreadGet a scheduled event by UUIDscheduledEvents.listcalendly.api.scheduledEvents.listreadList all scheduled events调度链接Scheduling LinksOperationOperation IDRiskDescriptionschedulingLinks.createcalendly.api.schedulingLinks.createwriteCreate a scheduling linkschedulingLinks.createSharecalendly.api.schedulingLinks.createSharewriteCreate a share link for an event typeschedulingLinks.createSingleUsecalendly.api.schedulingLinks.createSingleUsewriteCreate a single-use scheduling link用户UsersOperationOperation IDRiskDescriptionusers.getcalendly.api.users.getreadGet a user by UUIDusers.getAvailabilitySchedulecalendly.api.users.getAvailabilitySchedulereadGet a user availability scheduleusers.getCurrentcalendly.api.users.getCurrentreadGet the currently authenticated user (deprecated)users.listAvailabilitySchedulescalendly.api.users.listAvailabilitySchedulesreadList all availability schedules for a userusers.listBusyTimescalendly.api.users.listBusyTimesreadList busy times for a userusers.listEventTypescalendly.api.users.listEventTypesreadList event types for a user (deprecated)users.listMeetingLocationscalendly.api.users.listMeetingLocationsreadList meeting locations for a userWebhook 订阅OperationOperation IDRiskDescriptionwebhookSubscriptions.createcalendly.api.webhookSubscriptions.createwriteCreate a webhook subscriptionwebhookSubscriptions.deletecalendly.api.webhookSubscriptions.deletedestructiveDelete a webhook subscription [DESTRUCTIVE]webhookSubscriptions.getcalendly.api.webhookSubscriptions.getreadGet a webhook subscription by UUIDwebhookSubscriptions.listcalendly.api.webhookSubscriptions.listreadList webhook subscriptions从源码看这 54 个端点被组织成calendlyEndpointsNested嵌套树packages/calendly/index.ts#L173每棵子树对应packages/calendly/endpoints/下的一个实现文件如event-types.ts、scheduled-events.ts、invitees.ts等同时calendlyEndpointSchemaspackages/calendly/index.ts#L265为每个端点注册了 zod 输入/输出 schema供运行时校验与类型推导复用。端点实现细节以 Event Types 为例以事件类型域为例packages/calendly/endpoints/event-types.ts 展示了端点的通用实现范式每个端点函数都遵循同一结构调用 Calendly REST API通过makeCalendlyRequest见 packages/calendly/client.ts#L16发起请求。该客户端将 Calendly 官方 API 基地址https://api.calendly.com、版本1.0.0与Authorization: Bearer apiKey头统一封装并约定 GET/DELETE 携带 query、POST/PUT/PATCH 携带 JSON body任何网络异常都会被包装为CalendlyAPIError。写入本地持久化层当响应包含资源且ctx.db中存在对应表时用upsertByEntityId将资源按 UUID 写入数据库。例如eventTypes.get会从resource.uri的最后一段解析出 UUID 再落库同时规范化description_plain、created_at、updated_at字段。记录审计事件调用logEventFromContext(ctx, calendly.eventTypes.get, {...input}, completed)输出以calendly.为命名空间的操作日志便于追踪 Agent 对用户数据的每一次访问。对应地eventTypes.listAvailableTimes与eventTypes.listHosts是纯查询型端点只调用 API 并记录日志、不做落库eventTypes.updateAvailability则走POST event_types/{uuid}/user_availability_schedule子资源。这些细节解释了 README 中每个端点背后真实的 HTTP 语义也为排查「数据为何在数据库中」提供了依据。认证API Key 与首次使用提示README 明确指出插件的认证方式为API keyAuth: API key. Corsair prompts your tenant for credentials on first use.Corsair 在首次使用时提示你的租户提供凭据。这一行为在源码中得到精确印证。插件声明的认证配置为export const calendlyAuthConfig { api_key: { account: [organization, user_uri] as const, }, } as const satisfies PluginAuthConfig;见 packages/calendly/index.ts#L680——它表示凭据与「组织」和「用户 URI」两个账号维度绑定即同一个 Calendly 组织下可以区分不同用户为多租户隔离奠定基础。Key 的解析顺序由keyBuilderpackages/calendly/index.ts#L762决定Webhook 场景若插件配置了webhookSecret直接使用它作为 Webhook 签名校验密钥否则从ctx.keys.get_webhook_signature()读取缺失时抛出[auth-missing:calendly:webhook_signature]错误。端点场景若插件配置了options.key静态 Key直接使用否则按api_key类型从ctx.keys.get_api_key()读取缺失时抛出AuthMissingError(calendly, api_key)。因此你可以通过插件选项为所有租户提供一个静态 Key也可以依赖 Corsair 的密钥管理能力让每个租户独立提供自己的 Calendly API Key。两种方式的取值优先级为显式静态配置 租户密钥库。Webhooks6 类事件、签名校验与租户路由README 说明插件Handles 6 webhook events处理 6 个 Webhook 事件对应事件及其注册信息汇总如下注册于 packages/calendly/index.ts#L686 的calendlyWebhookSchemasWebhook 路径对应事件说明invitees.createdinvitee.created受邀人预订了会议invitees.canceledinvitee.canceled受邀人取消了会议invitees.noShowinvitee_no_show.created受邀人被标记为未出席routingForms.submissionrouting_form_submission.created路由表单提交创建eventTypes.updatedevent_type.updated事件类型被更新users.updateduser.updated用户信息被更新每个 Webhook 由「匹配器 处理器」组成实现分散在 packages/calendly/webhooks/ 目录的六个文件中invitee-created.ts、invitee-canceled.ts、invitee-no-show.ts、routing-form-submission.ts、event-type-updated.ts、user-updated.ts并通过 packages/calendly/webhooks/index.ts 聚合导出。事件匹配createCalendlyEventMatch(eventType)packages/calendly/webhooks/types.ts#L209生成匹配器解析原始请求体兼容字符串与已解析对象两种形态比对顶层event字段是否等于目标事件名请求体畸形时直接返回不匹配。签名校验HMAC-SHA256 防重放verifyCalendlyWebhookSignaturepackages/calendly/webhooks/types.ts#L230实现 Calendly 标准的签名验证流程从Calendly-Webhook-Signature请求头解析出ttimestamp,v1signature两个部分防重放将tUnix 秒级时间戳转为毫秒后与当前时间比较偏差超过5 分钟即拒绝Webhook timestamp is too old or invalid完整性用签名密钥对${timestamp}.${rawBody}计算HMAC-SHA256得到十六进制摘要与请求携带的v1值比对恒定时间比较使用crypto.timingSafeEqual比较原始字节缓冲避免时序侧信道攻击签名长度不一致直接判失败。每个 Webhook 处理器如inviteeCreated见 packages/calendly/webhooks/invitee-created.ts#L8都会先执行签名校验失败返回401校验通过后依据事件类型处理载荷将 invitee 与关联的 scheduled event 分别upsertByEntityId落库并输出calendly.webhook.inviteeCreated审计日志。多租户路由从载荷中解析组织/用户在 Corsair 多租户架构中同一个 Webhook 端点可能被多个租户共享。插件通过matchCalendlyTenantWebhookpackages/calendly/webhooks/tenant-matcher.ts#L75完成请求到租户的映射深度优先遍历 Webhook 载荷body.payload ?? body最大遍历深度 32查找形如https://api.calendly.com/organizations/uuid的字符串找到则返回{ linkType: organization, externalId: orgUri }作为租户匹配结果找不到组织 URI 时退而查找https://api.calendly.com/users/uuid返回{ linkType: user_uri, externalId: userUri }。由于 Calendly v2 Webhook 载荷中组织/用户 URI 出现在深层嵌套的各个位置该实现递归扫描数组与对象含organization字段优先匹配保证无论事件类型如何都能稳定路由。此外插件还在 packages/calendly/index.ts#L753 声明了pluginWebhookMatcher只要请求头包含calendly-webhook-signature即视为本插件的 Webhook 请求作为路由前的快速筛选。数据模型11 个实体与 zod Schema插件声明了一个版本化的数据库 Schemapackages/calendly/schema/index.ts版本号为1.0.0共 11 个实体全部以 zod 对象定义详见 packages/calendly/schema/database.ts实体用途关键字段节选scheduledEvents已排定事件id、uri、name、status(active/canceled)、start_time、end_time、location{type,location,join_url}eventTypes事件类型id、slug、scheduling_url、duration、kind、color、description_plain、activeinvitees受邀人id、email、name、status、event、timezoneusersCalendly 用户id、slug、email、scheduling_url、timezone、avatar_urlwebhookSubscriptionsWebhook 订阅callback_url、state(active/disabled)、scope、organization、userorgMemberships组织成员关系role、user_uri、user_email、organizationorgInvitations组织邀请organization、email、status、last_sent_at、usergroups群组name、slug、organization、user_count、scheduling_urlroutingForms路由表单name、organization、statusactivityLogEntries活动日志条目action、actor、details、namespace、occurred_atoutgoingCommunications外发通信记录channel、sent_at、status、to需要注意actor、details、tracking、questions_and_answers、payment等字段在 Calendly 官方 API 中形状随动作类型/来源而变化因此 schema 中采用z.record(z.string(), z.unknown())的开放结构既保证核心字段的类型安全又兼容不可枚举的扩展字段。时间字段统一使用z.coerce.date().nullable()以便持久化时归一化为 Date。这套 Schema 的价值在于插件端点如eventTypes.get和 Webhook 处理器会把 API 返回的 Calendly 资源自动同步到本地数据库Agent 查询历史数据、构建会话记忆时无需重复请求上游 API同时所有实体都有类型安全的读写入口。错误处理五级策略插件内置了针对 Calendly API 常见错误的分级处理策略packages/calendly/error-handlers.ts通过match判定错误类别、handler决定重试行为处理策略匹配条件HTTP 状态/消息行为RATE_LIMIT_ERROR429或消息含rate_limited/ratelimited/429maxRetries: 5并透传Retry-After头的等待时长headersRetryAfterMsAUTH_ERROR401或消息含invalid_auth/unauthorized/authentication failed/invalid_token告警日志maxRetries: 0不重试提示检查 API KeyPERMISSION_ERROR403或消息含permission_denied/forbidden/access_denied告警日志maxRetries: 0NOT_FOUND_ERROR404或消息含not_found告警日志maxRetries: 0DEFAULT兜底匹配所有错误错误日志maxRetries: 0所有日志均带有[CALENDLY:operation]前缀便于在服务端日志中按插件域过滤。此外CalendlyPluginOptions还暴露errorHandlers选项见 packages/calendly/index.ts#L70可以在插件初始化时用自定义处理器覆盖或扩充上述默认策略最终通过{ ...errorHandlers, ...options.errorHandlers }合并生效。权限配置按端点树精细裁剪 Agent 能力插件最值得关注的安全特性是permissions配置packages/calendly/index.ts#L76它控制 AI Agent 被允许执行的操作直接决定 Agent 能否读取、写入或删除用户数据。其类型为PluginPermissionsConfigtypeof calendlyEndpointsNested——以端点嵌套树的点分隔路径dot-notation进行覆盖路径写错会在编译期直接报类型错误calendly({ permissions: { // 只允许读取与创建禁止任何 destructive 操作 eventTypes.*: { riskLevel: [read, write] }, invitees.deleteData: false, // 显式禁用高危端点 scheduledEvents.cancel: false, // 禁止取消会议 }, });结合 README 中每个端点的 Risk 标注可以形成清晰的授权心智模型read类端点如users.listBusyTimes、routingForms.list风险最低write类端点如eventTypes.create、webhookSubscriptions.create会改动用户数据destructive类端点如invitees.deleteData、organizations.removeMember、scheduledEvents.deleteData会不可逆地删除数据README 中这类操作均以[DESTRUCTIVE]显式标注。上线前建议按最小权限原则逐域审查。插件选项速查综合 packages/calendly/index.ts#L64 的CalendlyPluginOptions定义插件支持的全部配置项如下选项类型说明authTypePickAuthapi_key认证方式默认api_keykeystring静态 Calendly API Key可选未提供时从租户密钥库读取webhookSecretstringWebhook 签名校验密钥可选未提供时从get_webhook_signature读取hooksInternalCalendlyPlugin[hooks]Corsair 生命周期钩子webhookHooksInternalCalendlyPlugin[webhookHooks]Webhook 处理钩子errorHandlersCorsairErrorHandler覆盖默认错误处理策略permissionsPluginPermissionsConfig...按端点树路径裁剪 Agent 权限非法路径为类型错误结语corsair-dev/calendly通过「端点 Webhook 数据模型 错误处理 权限」五位一体的设计把 Calendly 调度生态完整封装进 Corsair 的多租户框架54 个覆盖全资源域的端点让 Agent 能查日程、订会议、管组织6 类 Webhook 配合 HMAC-SHA256 签名校验与 5 分钟防重放窗口保证了回调的机密与安全组织/用户 URI 驱动的租户匹配让多租户部署开箱即用三级风险标注与编译期强类型的权限配置则把「Agent 越权」风险降到最低。插件描述与展示名可参见 packages/calendly/plugin-docs.yaml完整的端点入参/出参类型与 Webhook 载荷定义可在 packages/calendly/endpoints/types.ts 与 packages/calendly/webhooks/types.ts 中继续深入仓库中的 api.test.ts 则展示了各端点的行为验证方式。【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考