|
| 1 | +/** |
| 2 | + * The three connected ACCOUNTS — Google Drive, Zoho WorkDrive, Gmail (#352 Stage 1). |
| 3 | + * |
| 4 | + * These have been connectors in every respect except the registry: each stores its refresh |
| 5 | + * token through `saveConnectorRefreshToken` into `user_api_keys(user_id, provider=<id>)`, |
| 6 | + * which is byte-for-byte the row `connectors/client.ts` reads for an `auth:"oauth"` registry |
| 7 | + * connector, and Drive/WorkDrive already write `instance_connector_grants` under the same ids. |
| 8 | + * So declaring them is a DECLARATION, not a migration: there is no backfill, no rename, no |
| 9 | + * dual-write window. Existing connections keep working because nothing about the stored row |
| 10 | + * changes. |
| 11 | + * |
| 12 | + * `tools: []` is the point, not an omission. None of the three is a thing an agent calls |
| 13 | + * mid-turn: |
| 14 | + * |
| 15 | + * Drive / WorkDrive are INGESTION SOURCES. The user imports, or a `sync_connector` trigger |
| 16 | + * does, and the content then lives in the instance's own store where RAG and every other |
| 17 | + * surface already see it. Inventing `drive_read_file` so `capabilities.tools` has something |
| 18 | + * to gate would add a second content path into the model that bypasses the vector store, and |
| 19 | + * a second place the folder grant must be enforced per call — for a connector whose entire |
| 20 | + * security story today is one grant check at the top of a walk that cannot leave its root. |
| 21 | + * |
| 22 | + * Gmail's tool (`find_confirmation_link`) exists but is deliberately NOT creator-selectable: |
| 23 | + * it is granted at runtime by the owner's `AgentState.permissions.email` flag. The registry |
| 24 | + * has no category for that — `grantModel` is `user | instance-resource` and neither means |
| 25 | + * "granted by an owner-set permission flag" — so folding it in would mean inventing a third |
| 26 | + * grant model or softening the gate. It stays where it is. |
| 27 | + * |
| 28 | + * What the declaration buys is that "which connectors exist, and what are they" has ONE answer |
| 29 | + * (`GET /v1/connectors`) instead of five independent copies of "google_drive and zoho_workdrive |
| 30 | + * are the file connectors": two route files, three hand-written console blocks, an MCP |
| 31 | + * `PROVIDERS` map with a matching Zod enum, the `sync_connector` trigger branch, and the |
| 32 | + * apology comment in `client.ts`. |
| 33 | + * |
| 34 | + * A caveat that is real and unchanged from `google_sheets`: declaring `oauth` makes the generic |
| 35 | + * `/v1/connectors/:id/oauth/start` able to build an authorize URL whose `redirect_uri` is |
| 36 | + * `/v1/connectors/<id>/oauth/callback` — a path these providers' OAuth apps do not have |
| 37 | + * registered, so it would fail at the provider. The console does not link it; the dedicated |
| 38 | + * flows (`/v1/drive/google/start`, …) remain the live ones until #352 Stage 2 registers the |
| 39 | + * generic redirect and retires them. |
| 40 | + */ |
| 41 | +import { DRIVE_SCOPE } from "../drive.js"; |
| 42 | +import { GMAIL_SCOPE } from "../gmail.js"; |
| 43 | +import type { Connector } from "./types.js"; |
| 44 | + |
| 45 | +const GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; |
| 46 | +const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"; |
| 47 | + |
| 48 | +export const GOOGLE_DRIVE_CONNECTOR: Connector = { |
| 49 | + id: "google_drive", |
| 50 | + label: "Google Drive", |
| 51 | + auth: "oauth", |
| 52 | + // DRIVE_SCOPE is `drive.readonly`. There is no write path in lib/drive.ts and there is not |
| 53 | + // meant to be one, so `scopes.write:false` is enforceable truth: connectorClient refuses a |
| 54 | + // write-scoped token outright, and a read-only connector can never be write-consented (#90). |
| 55 | + scopes: { read: true, write: false }, |
| 56 | + // The grant model this connector already implements by hand: `requireConnectorGrant` on every |
| 57 | + // route, and the sync walk re-checking descent from the granted root. |
| 58 | + grantModel: "instance-resource", |
| 59 | + oauth: { |
| 60 | + authUrl: GOOGLE_AUTH_URL, |
| 61 | + tokenUrl: GOOGLE_TOKEN_URL, |
| 62 | + // `openid email` is what lets the status route say WHICH account is connected. |
| 63 | + scopes: ["openid", "email", DRIVE_SCOPE], |
| 64 | + clientIdEnv: "GOOGLE_CLIENT_ID", |
| 65 | + secretEnv: "GOOGLE_CLIENT_SECRET", |
| 66 | + }, |
| 67 | + tools: [], |
| 68 | +}; |
| 69 | + |
| 70 | +export const ZOHO_WORKDRIVE_CONNECTOR: Connector = { |
| 71 | + id: "zoho_workdrive", |
| 72 | + label: "Zoho WorkDrive", |
| 73 | + auth: "oauth", |
| 74 | + scopes: { read: true, write: false }, |
| 75 | + grantModel: "instance-resource", |
| 76 | + // No `oauth` block ON PURPOSE. Zoho's authorize/token endpoints are per data-centre |
| 77 | + // (`workDriveAccountsBase(env)` reads ZOHO_ACCOUNTS_BASE), so there is no static URL to |
| 78 | + // declare and the manifest shape cannot express one. Omitting it keeps the generic OAuth |
| 79 | + // routes answering 404 for this id rather than minting an authorize URL against the wrong |
| 80 | + // DC — the declaration says what is true, including the part that is not yet generic. |
| 81 | + tools: [], |
| 82 | +}; |
| 83 | + |
| 84 | +export const GMAIL_CONNECTOR: Connector = { |
| 85 | + id: "gmail", |
| 86 | + label: "Gmail", |
| 87 | + auth: "oauth", |
| 88 | + scopes: { read: true, write: false }, |
| 89 | + // `user`, not `instance-resource`: there is no per-mailbox grant row. Reach is decided by the |
| 90 | + // per-agent `permissions.email` flag, which lives in that agent's own state — see the header. |
| 91 | + grantModel: "user", |
| 92 | + oauth: { |
| 93 | + authUrl: GOOGLE_AUTH_URL, |
| 94 | + tokenUrl: GOOGLE_TOKEN_URL, |
| 95 | + scopes: ["openid", "email", GMAIL_SCOPE], |
| 96 | + clientIdEnv: "GOOGLE_CLIENT_ID", |
| 97 | + secretEnv: "GOOGLE_CLIENT_SECRET", |
| 98 | + }, |
| 99 | + tools: [], |
| 100 | +}; |
0 commit comments