Corsair Google Drive 插件实战:22 个类型化 API、OAuth 2.0 授权与本地数据库同步
【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair
@corsair-dev/googledrive是 Corsair 生态中的 Google Drive 集成插件,为 AI Agent 提供"连接用户自己的 Google Drive"的能力:一个客户端即可完成文件与文件夹的增删改查、共享、搜索、共享云端硬盘(Shared Drive)管理与存储配额查询,并自动把云端数据同步到本地数据库。本文基于该插件在仓库中的源码与文档,完整讲解它的安装方式、全部 22 个端点的操作语义与风险分级、OAuth 2.0 授权流程、本地实体同步机制以及driveChangedWebhook 的订阅与处理,读完后你可以直接在你的 Corsair 应用中接入 Google Drive 能力。
插件概览:一个插件能做什么
Google Drive 插件围绕googledrive这一个插件对象展开,核心产物来自 index.ts 中的googledrive()工厂函数:
- 22 个类型化 API 操作,按
files、folders、sharedDrives、search、storage五组组织,每个操作都有基于 zod 的输入/输出校验; - 3 个同步实体:
files、folders、sharedDrives,读写操作后自动 upsert 到本地数据库(见 schema/database.ts); - 1 个入站 Webhook 事件
driveChanged,监听文件/文件夹的创建、更新与删除。
插件的声明定义在 index.ts:googleDriveEndpointsNested将 22 个端点组织成files.list、folders.create、storage.getQuota这样的点分命名空间,googleDriveWebhooksNested声明唯一的driveChanged事件。端点树结构同时被用作permissions配置的类型约束——配置里写错路径会直接产生 TypeScript 类型错误。
安装与初始化
安装
在你的 Corsair 项目中安装插件(以 pnpm 为例,仓库 package.json 中声明了 npm/yarn/pnpm/bun 均可用):
pnpm add @corsair-dev/googledrive插件以corsair(要求>=0.1.120)和zod(^4.1.13)为 peer 依赖,运行时还会用到corsair/core与corsair/http提供的上下文、OAuth token 获取、HTTP 请求与 Webhook 基础设施。
注册插件
官方文档(docs/plugins/googledrive/overview.mdx)给出了完整的初始化示例:
// corsair.ts import Database from 'better-sqlite3'; import { createCorsair } from 'corsair'; import { googledrive } from '@corsair-dev/googledrive'; export const corsair = createCorsair({ plugins: [ googledrive(), ], database: new Database('corsair.db'), kek: process.env.CORSAIR_KEK!, hub: { projectApiKey: process.env.CORSAIR_API_KEY!, signingSecret: process.env.CORSAIR_SIGNING_SECRET!, }, });多租户是默认形态:通过corsair.withTenant(id)圈定租户后再调用插件 API。连接租户时,调用管理接口生成连接链接并引导用户浏览器跳转:
const { connectUrl } = await corsair.manage.connect.createLink({ plugin: 'googledrive', tenantId: 'acme', }); // 将用户浏览器重定向到 connectUrl首次使用时,Corsair 会提示租户完成凭据授权(README 中 Auth 一节的说明),之后所有端点调用都会自动携带该租户的访问令牌。
端点总览:22 个操作与风险分级
README 的 Endpoints 一节完整列出了全部 22 个端点。下表完整继承该清单,并按源码 index.ts 中googleDriveEndpointMeta声明的风险分级补充了说明:
| 操作 | Operation ID | 风险 | 说明 |
|---|---|---|---|
files.copy | googledrive.api.files.copy | write | 复制 Google Drive 中的文件 |
files.createFromText | googledrive.api.files.createFromText | write | 从文本内容创建新的 Drive 文件 |
files.delete | googledrive.api.files.delete | destructive | 永久删除文件 [破坏性 · 不可逆] |
files.download | googledrive.api.files.download | read | 下载文件内容 |
files.get | googledrive.api.files.get | read | 获取指定文件的元数据 |
files.list | googledrive.api.files.list | read | 列出 Google Drive 中的文件 |
files.move | googledrive.api.files.move | write | 将文件移动到不同文件夹 |
files.share | googledrive.api.files.share | write | 通过授予权限共享文件 |
files.update | googledrive.api.files.update | write | 更新文件内容或元数据 |
files.upload | googledrive.api.files.upload | write | 上传文件到 Google Drive |
folders.create | googledrive.api.folders.create | write | 创建新文件夹 |
folders.delete | googledrive.api.folders.delete | destructive | 永久删除文件夹及其内容 [破坏性 · 不可逆] |
folders.get | googledrive.api.folders.get | read | 获取指定文件夹的元数据 |
folders.list | googledrive.api.folders.list | read | 列出 Google Drive 中的文件夹 |
folders.share | googledrive.api.folders.share | write | 通过授予权限共享文件夹 |
search.filesAndFolders | googledrive.api.search.filesAndFolders | read | 搜索 Google Drive 中的文件和文件夹 |
sharedDrives.create | googledrive.api.sharedDrives.create | write | 创建新的共享云端硬盘 |
sharedDrives.delete | googledrive.api.sharedDrives.delete | destructive | 永久删除共享云端硬盘 [破坏性 · 不可逆] |
sharedDrives.get | googledrive.api.sharedDrives.get | read | 获取共享云端硬盘信息 |
sharedDrives.list | googledrive.api.sharedDrives.list | read | 列出共享云端硬盘 |
sharedDrives.update | googledrive.api.sharedDrives.update | write | 更新共享云端硬盘 |
storage.getQuota | googledrive.api.storage.getQuota | read | 获取用户的 Google Drive 存储配额与用量 |
风险分级(riskLevel)是插件安全模型的核心:read类操作仅读取数据;write类操作会修改云端数据,但可恢复;destructive类操作(三个delete)被标记为irreversible: true,用于 MCP 服务器的权限系统决定 allow / deny / require_approval。README 中所有标有[DESTRUCTIVE · IRREVERSIBLE]的操作,在调用前都应经过审批或强确认。
API 调用实践:典型操作与输入参数
插件所有端点都通过tenant.googledrive.api.*路径调用。这里结合 endpoints/types.ts 中的 zod 输入 schema 与各端点实现,给出最常用的几类调用。
列出文件
files.list支持 Google Drive 标准查询参数(q查询串、pageSize分页大小、pageToken翻页游标、spaces、corpora、driveId、orderBy、supportsAllDrives等):
const tenant = corsair.withTenant('acme'); const result = await tenant.googledrive.api.files.list({ pageSize: 50, q: "mimeType='text/plain'", });实现上,files.list会请求GET /files并把返回的每条记录按 mimeType 判断后 upsert 进本地files或folders表(endpoints/files.ts),因此列表结果可以直接在本地复用。
从文本创建文件
const tenant = corsair.withTenant('acme'); const file = await tenant.googledrive.api.files.createFromText({ name: 'hello.txt', content: 'Hello from Corsair', mimeType: 'text/plain', // 可选 parents: ['folderId'], // 可选,指定父文件夹 description: 'demo', // 可选 });从源码看,createFromText以uploadType: 'multipart'发起POST /files,创建成功后自动回查files.get刷新最新元数据(endpoints/files.ts)。files.upload的入参与其一致(name、mimeType、parents、description),同样走 multipart 上传。
更新、复制与移动文件
// 更新元数据:重命名、加星、移入回收站、改父目录 await tenant.googledrive.api.files.update({ fileId: 'FILE_ID', name: 'renamed.txt', starred: true, trashed: false, addParents: 'newFolderId', removeParents: 'oldFolderId', }); // 复制 await tenant.googledrive.api.files.copy({ fileId: 'FILE_ID', name: 'copy-of-file.txt', parents: ['targetFolderId'], }); // 移动 await tenant.googledrive.api.files.move({ fileId: 'FILE_ID', addParents: 'targetFolderId', removeParents: 'sourceFolderId', });files.update在实现中通过PATCH /files/{fileId}合并提交name、description、starred、trashed、parents、properties、appProperties,并把addParents/removeParents/supportsAllDrives等作为查询参数透传(endpoints/files.ts)。
下载与共享
// 下载文件原始内容(返回二进制流,形状取决于文件类型) const binary = await tenant.googledrive.api.files.download({ fileId: 'FILE_ID', acknowledgeAbuse: false, }); // 共享文件:对"任何人"授予只读 await tenant.googledrive.api.files.share({ fileId: 'FILE_ID', type: 'anyone', role: 'reader', });files.share会POST /files/{fileId}/permissions,type可选user/group/domain/anyone,role可选owner/organizer/fileOrganizer/writer/commenter/reader,还支持expirationTime(过期时间)、sendNotificationEmail、transferOwnership等高级参数。folders.share的参数与行为一致(endpoints/folders.ts)。
搜索与配额
// 跨文件与文件夹搜索,q 为必填 await tenant.googledrive.api.search.filesAndFolders({ q: "name contains 'report'", pageSize: 20, }); // 存储配额:limit 总量、usage 已用、usageInDrive / usageInDriveTrash const quota = await tenant.googledrive.api.storage.getQuota();storage.getQuota请求GET /about?fields=storageQuota,若响应缺少storageQuota会抛出GoogleDriveAPIError('Google Drive about.get returned no storageQuota', 502)(endpoints/storage.ts)。
认证机制:OAuth 2.0 与令牌生命周期
README 明确:插件使用OAuth 2.0,Corsair 会在首次使用时提示租户提供凭据。源码层面的完整配置见 index.ts:
export const googledriveAuthConfig = { oauth_2: { account: ['channel_id', 'changes_page_token'] as const, }, };- 授权端点:
https://accounts.google.com/o/oauth2/v2/auth - 令牌端点:
https://oauth2.googleapis.com/token - Scope:
https://www.googleapis.com/auth/drive(完整读写权限) - 授权参数:
access_type: 'offline'(获取可用于刷新的 refresh token)、prompt: 'consent'
keyBuilder在调用端点时通过getOAuthAccessToken(ctx, { plugin: 'googledrive', tokenUrl: ... })解析当前租户的访问令牌;若 authType 不是oauth_2或取不到令牌,则抛出AuthMissingError('googledrive', 'oauth_2')。同时支持传入options.key作为静态 key 兜底。
底层 HTTP 客户端见 client.ts:所有请求以https://www.googleapis.com/drive/v3为 base,走corsair/http的request。makeAuthenticatedGoogleDriveRequest内置 401 自动重试——遇到 401 且存在_refreshAuth回调时,先刷新令牌再重放请求,对上层调用透明。
本地数据库同步:files / folders / sharedDrives
插件把 Google Drive 数据建模为三类本地实体(schema/database.ts):
- files:文件实体,除 Drive 元数据(
name、mimeType、parents、trashed、size、webViewLink、sha256Checksum等)外,额外包含filePath与createdAt; - folders:文件夹实体,结构类似 files,mimeType 固定为
application/vnd.google-apps.folder; - sharedDrives:共享云端硬盘实体(
name、themeId、colorRgb、hidden)。
几乎每个读端点都会在返回结果后把记录upsertByEntityId到对应表;删除端点则deleteByEntityId同步清理本地数据。这样 Agent 就可以不命中 Drive API直接查询已同步数据:
const tenant = corsair.withTenant('acme'); const rows = await tenant.googledrive.db.files.search({ data: { trashed: false }, limit: 50, });plugin-docs.yaml(packages/googledrive/plugin-docs.yaml)中给出的 db 示例正是"搜索未进回收站的文件、无需再请求 Drive",适合作为常用查询范式。
Webhook:driveChanged 事件
README 说明插件处理1 个 Webhook 事件(driveChanged)。它基于 Google Drive 的changes feed + Watch channel机制实现:文件/文件夹被创建、更新或删除时触发,属于典型的"入站 Webhook + 拉取增量"混合模型。
订阅原理
googledriveSubscribe(subscribe.ts)在租户连接时执行:
- 用访问令牌请求
GET /changes/startPageToken获取起始页令牌; - 将令牌持久化到租户 keys(
set_changes_page_token); - 调用
googleChannelSubscribe在GET /changes/watch?pageToken=...上打开共享的 Watch channel——覆盖整个 Drive(单个文件需要单独的文件级 channel,插件选择整盘监听)。
处理器挂载
将 Corsair 的 HTTP handler 挂载到任意框架路由(docs/plugins/googledrive/webhooks.mdx 示例为 Next.js App Router):
// app/api/webhook/route.ts import { processWebhook } from "corsair"; import { corsair } from "@/server/corsair"; export async function POST(request: Request) { const headers = Object.fromEntries(request.headers); const body = await request.json(); const result = await processWebhook(corsair, headers, body); return result.response; }插件通过pluginWebhookMatcher做前置过滤:校验请求头from: noreply@google.com或user-agent包含APIs-Google,并解码 Pub/Sub 消息确认resourceUri与 drive 相关(index.ts)。
事件载荷与响应数据
driveChanged的响应数据(DriveChangedEventSchema,见 webhooks/types.ts)核心字段:
{ type: 'fileChanged' | 'folderChanged', fileId?: string, folderId?: string, changeType: 'created' | 'updated' | 'deleted' | 'trashed' | 'untrashed', file?: File, folder?: File, filePath?: string, // 例如 /folder/sub/file.txt change?: Change, binaryData?: string | null, // 文件内容 base64(文件事件时) allFiles: [...], // 本次变化涉及的全部文件 allFolders: [...], // 本次变化涉及的全部文件夹 }用webhookHooks处理事件:
googledrive({ webhookHooks: { driveChanged: { after: async (ctx, result) => { console.log('Drive change:', result.data?.changeType, result.data?.fileId ?? result.data?.folderId); }, }, }, });增量消费的健壮性设计
driveChanged的处理逻辑(webhooks/changes.ts)值得关注:
- 分页拉满:
fetchAllChanges逐页消费 changes feed(每页 100 条),循环中记录已请求的 page token 防止环状引用导致重复拉取,并以MAX_CHANGE_PAGES = 10封顶避免大积压拖垮 Webhook 响应; - 断点续传:最终页返回的
newStartPageToken作为下一轮游标写入租户 keys;若被截断则记录resumeToken供下一条通知继续,并用 CAS(compare-and-set)防止并发覆盖; - 失效令牌恢复:当存储的游标过期(400
invalidStartPageToken)且与通知 URI 中的 token 不一致时,自动回退到 URI 中携带的 token 重新拉取; - 变更归类:
removed标记映射为deleted,file.trashed映射为trashed,并计算文件路径(buildFilePath向上回溯父目录、深度上限 20); - 事件归并:一次通知可能包含多个变化,返回数据中的
allFiles/allFolders汇总全部受影响对象,首个变化作为主事件。
测试与验证
仓库为插件提供了完整的测试保障,是理解端点的最佳佐证:
- api.test.ts:面向真实 Google API 的集成测试(需要
GOOGLE_ACCESS_TOKEN环境变量),逐一对 files/folders/sharedDrives/storage/search 五组端点做"创建 → 操作 → 校验输出 schema → 清理"闭环,并用GoogleDriveEndpointOutputSchemas.*校验返回类型; - webhooks/changes.test.ts、subscribe.test.ts 与 client.test.ts 覆盖 Webhook 处理与 HTTP 客户端行为;
jest作为测试运行器,package.json中提供pnpm test与pnpm typecheck脚本。
结语与进阶阅读
至此,你已经掌握了@corsair-dev/googledrive的核心用法:22 个类型化端点的调用方式与风险分级、OAuth 2.0 授权与 401 自动刷新、三类本地实体的自动同步,以及driveChangedWebhook 的订阅与增量消费。继续深入可参考仓库中的以下位置:
- 插件声明与配置:packages/googledrive/index.ts
- 全部输入/输出 zod schema:packages/googledrive/endpoints/types.ts
- 本地实体模型:packages/googledrive/schema/database.ts
- Webhook 处理细节:packages/googledrive/webhooks/changes.ts
- 集成测试用例:packages/googledrive/api.test.ts
- 官方使用文档:docs/plugins/googledrive/overview.mdx 与 docs/plugins/googledrive/webhooks.mdx
如需了解如何将插件的操作暴露为 MCP 工具给 Agent 使用,可进一步查看 docs/mcp-adapters/mcp-adapters.mdx。
【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考