OOMOL Connector SDK 开发指南
最后更新于 2026 年 6 月 29 日
@oomol-lab/connector 是 OOMOL Connector 网关的轻量、零依赖 TypeScript 客户端。通过一行带类型的代码即可调用 provider action,例如 gmail.search_threads、slack.post_message、notion.append_block。鉴权、OAuth、token 刷新和凭证存储由网关处理;SDK 负责构造请求并解析返回。无需 codegen,也无需 CLI。
主要内容:
- 如何安装 SDK、获取 API key,并完成第一次调用。
- 描述整个模型只需五个词:gateway、provider、action、connection、organization。
- 两种调用路径(动态字符串与 namespace),以及如何读取执行元数据。
- 如何在不做任何 codegen 的前提下,开启每个 action 的精确类型。
- 如何透传尚未建模的 endpoint、检视 catalog、列出已连接的 apps。
- 如何用
ProjectConnector代你的终端用户连接账号并执行 action。 - 错误长什么样、哪些可重试,以及如何为调用设置作用域、超时和取消。
安装
npm install @oomol-lab/connector # 或:bun add / pnpm add / yarn add
需要 Node ≥ 18(依赖内置的 fetch 和 AbortController)。SDK 只发布 dist,零运行时依赖,且 sideEffects: false,可以干净地做 tree-shaking。它可运行于 Node、Bun、Deno、边缘运行时和浏览器环境;在浏览器中使用时,由宿主注入 API key。
获取 API key
你需要一个 OOMOL Connector 的个人 API key,形如 api_…。在 OOMOL Console 创建:
https://console.oomol.com/api-key
把它设为环境变量;本指南统一使用 OOMOL_API_KEY。每个请求都会由网关授权,并以 Authorization: Bearer <apiKey> 发送。
个人
api_…key 在你自己的 connection 上执行 action。代你的终端用户连接账号时,使用项目 key(oo_proj_…)和ProjectConnector客户端;见为你的用户连接账号。
快速上手
构造客户端后即可调用 action。下面两种写法等价,可任选一种。
import { Connector } from "@oomol-lab/connector";
const oomol = new Connector({ apiKey: process.env.OOMOL_API_KEY! });
// 路径 1:动态字符串。可调用任意 action id。
const { threads } = await oomol.execute("gmail.search_threads", { query: "from:boss" });
// 路径 2:namespace 语法糖。底层仍是同一个调用。
const result = await oomol.gmail.search_threads({ query: "is:unread" });
execute 直接返回 action 的输出。当你还想要执行元数据时,用 executeRaw:
const raw = await oomol.executeRaw("gmail.search_threads", { query: "from:ceo" });
raw.data; // 和 execute() 的返回值相同
raw.executionId; // 服务端分配的执行 id(用于工单支持 / 日志关联)
raw.actionId; // 回显的 action id
raw.message; // 成功响应信封里的可读消息
核心调用流程由 execute 和 executeRaw 组成。
概念
SDK 模型由五个核心概念组成,鉴权、凭证和实际 provider 调用都由网关处理:
| 术语 | 含义 |
|---|---|
| Gateway(网关) | 这个客户端对接的 OOMOL Connector 托管服务。它持有凭证、真正发起对 provider 的调用,并返回统一的信封。SDK 在本地不运行任何集成逻辑。 |
| Provider / service | 一个第三方 API(gmail、slack、github、notion……)。它就是 action id 的 <service> 前缀。 |
| Action | provider 上的一个操作,标识为 "<service>.<action>"(例如 gmail.search_threads)。action 由网关提供,客户端负责调用。 |
| Connection(连接) | 某个 provider 已授权、已存储的凭证。你从不直接接触 token;只用 connectionName 指明用哪一个连接。OAuth 和凭证生命周期是网关的职责。 |
| Organization(组织) | 可选的租户作用域,以 x-oo-organization-name 头发送。 |
同一套词汇贯穿整个 SDK 接口:action id 始终是 "<service>.<action>",connectionName 用来选择已存储的连接;在项目客户端中,externalUserId 标识你的某个终端用户。
常见操作
| 想要…… | 用 | 说明 |
|---|---|---|
| 执行一个已建模的 action | execute / executeRaw | 带类型的一行调用。executeRaw 还会返回 { executionId, actionId, message }。 |
| 调用尚未建模为 action 的 endpoint | proxy | 透传到上游 API,由网关注入该连接的凭证。 |
| 把 action 喂给 LLM / 构建动态表单 | catalog | 任意 action 或 provider 的运行时 JSON Schema(2020-12)。 |
| 查看已经连了什么 | apps.list | 只读列出你已经建立的连接。 |
| 让你的用户连接他们自己的账号 | ProjectConnector | 一个独立的、按项目作用域的客户端,用来代你的终端用户连接账号并执行 action。 |
provider 和 action 的覆盖由网关提供。当前支持 600+ 个 provider,并持续增加;可通过 oomol.catalog.providers() 在运行时发现。
精确类型(可选)
动态字符串路径对任意 actionId 都能编译通过。默认情况下,每个 action 使用宽松类型(入参和出参都是 Record<string, any>),便于先完成调用。需要精确入/出参类型和 JSDoc 时,安装配套类型包,并为用到的每个 provider 添加一行 side-effect import:
import { Connector } from "@oomol-lab/connector";
import "@oomol-lab/connector-types/gmail"; // gmail.* 的精确类型 + JSDoc
import "@oomol-lab/connector-types/slack"; // ……以及 slack.*
const oomol = new Connector({ apiKey: process.env.OOMOL_API_KEY! });
await oomol.gmail.search_threads({ query: "from:boss" }); // 入参与出参现在都是精确类型
await oomol.notion.append_block({ pageId, text }); // 没 import notion → 仍可宽松调用
npm install -D @oomol-lab/connector-types
无需 codegen、无需提交生成文件,也不需要 CLI。已注册的 action 会得到字面量补全和精确的入/出参类型;未注册的会退化为 Record<string, any>。即使类型包落后于后端,新 action 也可以继续通过宽松类型调用。核心运行时不依赖类型包。
需要把
moduleResolution设为bundler、node16或nodenext,子路径 import(@oomol-lab/connector-types/gmail)才能解析。配置细节见@oomol-lab/connector-types仓库。
配置
除 apiKey 外每个字段都是可选的:
new Connector({
apiKey: process.env.OOMOL_API_KEY!, // 必填
baseUrl: "https://connector.oomol.com/v1", // 默认值
organization: "acme", // 默认组织 → x-oo-organization-name
connectionName: "work", // 默认连接(更推荐按调用 / using() 设置)
timeoutMs: 30_000, // 默认单次请求超时
maxRetries: 2, // 默认;对 429 / 5xx / 网络错误用退避 + 抖动重试
fetch: customFetch, // 用于测试 / 代理 / 链路追踪时注入
});
| 字段 | 默认值 | 说明 |
|---|---|---|
apiKey | — | 必填。以 Authorization: Bearer <apiKey> 发送。 |
baseUrl | https://connector.oomol.com/v1 | 仅支持手动覆盖;没有基于环境变量的自动切换。 |
organization | — | 调用在哪个租户下运行。 |
connectionName | — | 当某个 provider 有多个连接时,选用哪一个。单连接场景可作为客户端默认值;多连接场景建议按调用或用 using() 设置。 |
timeoutMs | 30_000 | 单次请求超时(毫秒)。 |
maxRetries | 2 | 对 429 / 5xx / 网络错误重试,采用指数退避 + 抖动。 |
fetch | 全局 fetch | 为测试、代理 agent 或链路追踪注入自定义 fetch。 |
作用域与按调用选项
三个层级按此优先级解析:按调用选项 > using() 作用域 > 客户端默认值。
using() 返回一个不可变的、合并了给定默认值的子客户端;原客户端不受影响:
const work = oomol.using({ connectionName: "work", organization: "acme" });
await work.gmail.search_threads({ query: "label:urgent" }); // 在 "work" / "acme" 下运行
按调用选项只作用于本次调用,并具有最高优先级:
await oomol.execute(
"gmail.search_threads",
{ query: "from:ceo" },
{
organization: "acme", // 本次调用覆盖组织
connectionName: "alt", // 本次调用选用另一个连接
timeoutMs: 10_000, // 本次调用用更紧的超时
retries: 0, // 本次调用禁用重试
signal: controller.signal, // 传入 AbortSignal
},
);
connectionName 按同样的层级解析。它在传输层以 x-oo-connector-alias 头携带(网关字段名为 alias),SDK 接口统一使用 connectionName。
两个客户端一览
这个包导出两个客户端,分别面向个人连接和项目下的终端用户连接。它们共用传输和错误模型;API key、方法和类型相互独立。
Connector | ProjectConnector | |
|---|---|---|
| API key | 个人 api_… | 项目 oo_proj_… |
| 作用对象 | 你自己的连接 | 你的终端用户的连接 |
| 如何标识用户 | — | externalUserId(由你选定) |
| 接口 | execute、proxy、catalog、apps、namespace | connect.*、waitForConnection、execute、forUser |
| 用途 | 调用你已连接的 provider | 构建 SaaS 产品,让每个用户连接自己的账号 |
下面先看个人客户端 Connector;ProjectConnector 见为你的用户连接账号。
Proxy:调用还没有 action 的 endpoint
当网关还没有把某个 endpoint 建模成 action 时,可用 proxy 直接访问。网关仍会注入该连接的凭证;请求和响应保持上游 API 的原始形态。
// 带类型的 GET。注意字段是 `endpoint`,不是 `path`。
const repos = await oomol.proxy<Array<{ name: string }>>("github", {
endpoint: "/user/repos",
method: "GET",
query: { per_page: 5, sort: "updated" },
});
repos.status; // 上游 HTTP 状态码
repos.data.map((r) => r.name);
// 带 body 和上游 header 的 POST(这些 header 发给 provider,不是网关)。
await oomol.proxy("github", {
endpoint: "/repos/acme/widgets/issues",
method: "POST",
headers: { "X-GitHub-Api-Version": "2022-11-28" },
body: { title: "Tracking issue", labels: ["chore"] },
});
endpoint 既接受路径(相对于 provider 的 base URL 解析),也接受完整 URL,适用于带区域域名的 provider,例如 https://eu.posthog.com/api/...。method 是 GET | POST | PUT | PATCH | DELETE 之一。响应是 { status, headers, data }。proxy 的 body 在后端是 strict 的:未知顶层 key 会以 invalid_input 拒绝。
Catalog:检视 provider 与 action
catalog 提供只读运行时元数据,可用于动态表单、校验和 LLM 工具定义。入/出参 schema 使用 JSON Schema(2020-12),与编译期类型包相互独立。
// 列出 provider;可选地在服务端收窄。
const all = await oomol.catalog.providers(); // 全部 provider
const mailish = await oomol.catalog.providers({ q: "mail" }); // 全文搜索 → ?q=
const some = await oomol.catalog.providers({ service: ["gmail", "slack"] }); // 限定 → ?service=…
// 某个 service 的全部 action。
const actions = await oomol.catalog.actions("gmail");
// 单个 action 的完整元数据,包含运行时 JSON Schema。
const meta = await oomol.catalog.action("gmail.search_threads");
meta.name; // 可读名称
meta.requiredScopes;// 该 action 需要的 OAuth scope
meta.inputSchema; // 入参的 JSON Schema(2020-12)
meta.outputSchema; // 出参的 JSON Schema(2020-12)
每个 provider 携带 { service, displayName, iconUrl, homepageUrl, categories, authTypes }。
Apps:列出你已连接的账号
apps.list() 返回网关已持有连接的只读视图。连接创建和移除在 Console 中完成。
const apps = await oomol.apps.list();
for (const app of apps) {
// { id, service, status, connectionName, … };未设置时 connectionName 为 null。
console.log(`${app.service}: id=${app.id} status=${app.status} connectionName=${app.connectionName}`);
}
// 把某个连接的 connectionName 作为按调用选择器传回,即可指定它。
const work = apps.find((a) => a.connectionName === "work");
if (work) {
await oomol.execute("gmail.search_threads", { query: "is:unread" }, { connectionName: "work" });
}
错误处理
失败会抛出带类型的 ConnectorError。调用方主动取消(被中止的 AbortSignal)会抛出标准 AbortError,可与网关或传输错误区分。
import { Connector, ConnectorError, isRetryable } from "@oomol-lab/connector";
try {
await oomol.slack.post_message({ channel: "#general", text: "shipped" });
} catch (err) {
if (err instanceof ConnectorError) {
err.code; // 可判别联合,例如 "rate_limited"、"credential_expired"
err.status; // HTTP 状态码(客户端 / 网络错误时为 0)
err.requestId; // 失败关联 id
err.actionId; // 适用时
err.executionId; // 适用时
err.data; // 上游响应体,例如 provider_error 时
if (isRetryable(err)) {
// 429 / 5xx / 网络 / rate_limited / proxy_upstream_timeout / request_in_progress
}
} else {
throw err; // 非 ConnectorError,例如调用方取消产生的 AbortError;重新抛出
}
}
err.code 是一个开放联合:已知后端码有自动补全,新的后端码也会作为字符串透传。处理时建议保留默认分支。常见错误码按组如下:
| 分组 | 错误码 |
|---|---|
| 输入 / 请求 | invalid_input、invalid_request_payload、invalid_request_signature |
| App / provider | app_not_found、app_not_ready、app_auth_type_mismatch、provider_not_found、provider_not_configured、provider_config_not_found、provider_error |
| 凭证 / 鉴权 | credential_expired、scope_missing、user_oauth_client_required |
| 连接选择 | connection_ambiguous、connection_account_conflict、connection_alias_conflict、connection_request_not_found、connected_account_not_found |
| Proxy | proxy_not_supported、proxy_upstream_error、proxy_upstream_timeout、proxy_response_too_large |
| 限流 / 并发 | rate_limited、request_in_progress、request_key_conflict、request_key_used |
| 仅客户端(status 0,请求未发出或传输失败) | client_invalid_request、client_timeout、client_network_error、client_wait_timeout |
isRetryable(err) 对 rate_limited、proxy_upstream_timeout、request_in_progress、HTTP 429、任意 5xx 以及传输失败(status 0)返回 true。对客户端校验错误(client_invalid_request)和 waitForConnection 的等待上限(client_wait_timeout)返回 false;这两类错误通常需要修改调用或等待逻辑。
取消与超时
传入 AbortSignal 可取消调用;设置 timeoutMs 可限定单次调用。内置重试层会处理瞬时失败;需要一次确定性的单次尝试时,可设置 retries: 0。
const controller = new AbortController();
setTimeout(() => controller.abort(), 50);
try {
await oomol.execute("gmail.search_threads", { query: "huge" }, { signal: controller.signal });
} catch (err) {
(err as Error).name; // "AbortError"
}
为你的用户连接账号
Connector 在你自己的连接上执行 action。ProjectConnector 面向 SaaS 产品:你的终端用户通过你的应用连接他们自己的 Gmail / Slack / GitHub / …… 账号,然后由你的后端代他们执行 action。这属于托管鉴权(managed auth)模型,使用方式类似 Composio 和 Pipedream Connect。
托管鉴权的要点是:凭证从不经过你的应用或任何模型。用户在网关托管页面上授权;网关存储凭证并自动刷新 token。你的代码只持有不透明标识符。
终端用户标识符
externalUserId 是项目客户端的用户隔离键,由你选定,通常使用你自己数据库里的用户 id。连接账号、等待连接、执行 action 等项目操作都按它作用域。始终传入一致的 externalUserId,网关会让每个用户的连接彼此隔离。
构造项目客户端
ProjectConnector 是一个独立客户端,用项目 API key(oo_proj_…)构造。它只暴露项目作用域操作,不包含个人客户端的 execute / namespace / proxy / catalog / apps 接口。
import { ProjectConnector } from "@oomol-lab/connector";
const project = new ProjectConnector({ apiKey: process.env.OOMOL_PROJECT_API_KEY! }); // oo_proj_...
先在 Console 完成配置。 在你的后端连接账号之前,需要由管理员在 OOMOL Console 创建项目、服务配置(provider config)和项目 API key。一次性配置和对应的后端 REST 流程见 OOMOL Connector SaaS 使用指南。本 SDK 是同一套运行时 API 的带类型封装。
OAuth:创建链接,再等待完成
OAuth 分两步:创建待处理的连接请求,引导用户授权,再等待完成。
// 1. 为你的某个用户创建一个待处理的连接请求。
const request = await project.connect.oauth("user_42", {
service: "gmail",
connectionName: "work", // 要赋予的名字;之后用它来指定这个账号
returnUri: "https://app.example.com/connected", // 回调完成后网关把用户带回到这里
});
// 2. 把用户带到 provider 的授权页面。
redirectUserTo(request.authorizationUrl);
// 3. 轮询直到用户完成(或失败 / 过期)。返回最终的连接请求。
const connected = await project.waitForConnection(request);
connected.status; // "connected" | "failed" | "expired"
connected.connectedAccountId; // 连接成功后存储的账号 id
authorizationUrl 会先打开 Connector 托管的授权入口页,展示你的项目和用户将要授权的 provider,然后再把用户带到 provider 的 OAuth 页面。

waitForConnection 会轮询到请求离开 initiated 状态后返回它。如果用户未完成授权,请求会自然变为 expired。maxWaitMs(默认 600_000 毫秒,与请求过期时间一致)先耗尽时,会抛出错误码为 client_wait_timeout 的 ConnectorError;被中止的 signal 会抛出标准 AbortError。
当你的 returnUri 被命中时,网关会追加一些 query 参数,你可以在回调页面读取:
status=success
service=gmail
providerConfigId=pc-1
externalUserId=user_42
connectedAccountId=ca-1
……或者在取消 / provider 出错时:
status=error
code=<connector-error-code>
message=<human-readable-message>
API key / 自定义凭证:同步返回
API key 和自定义凭证连接会立即返回账号;只有 OAuth 需要 waitForConnection。
// 终端用户自己的上游 key(例如 OpenAI 的 sk-…),不要填写 oo_proj_ key。
const account = await project.connect.apiKey("user_42", { service: "openai", apiKey: "sk-..." });
account.available; // 该账号此刻是否可以执行 action
// provider 特有的凭证字段,由网关按 provider config 校验。
await project.connect.customCredential("user_42", {
service: "jira",
values: { email: "user@acme.com", token: "..." },
});
每个 connect.* 调用用 service 或 providerConfigId 中恰好一个来标识 provider。当一个项目对同一 service 有多个配置时用 providerConfigId;简单情形用 service。
代用户执行 action
// provider service 从 actionId 前缀("gmail")推导。
// 未传 connectionName / connectedAccountId 时,使用该用户最新的 active 账号。
const out = await project.execute(
"user_42",
"gmail.search_threads",
{ query: "is:unread" },
{ connectionName: "work" },
);
账号选择优先级:connectedAccountId(指定某个账号)优先于 connectionName(按名字选账号);两者都不传时,网关使用该用户在该 provider 下最新的 active 账号。project.executeRaw 返回与个人客户端相同的 { data, executionId, actionId, message } 信封。
绑定到单个用户
forUser 可预先绑定 externalUserId,后续调用不必重复传 id:
const user = project.forUser("user_42");
await user.connect.oauth({ service: "slack" });
const slack = await user.waitForConnection(/* 上面的请求 */ "req-id");
await user.execute("slack.post_message", { channel: "#general", text: "shipped" });
project.execute 复用与个人路径相同的 @oomol-lab/connector-types 注册表:已 import 的 provider 得到精确的入/出参,其余保持宽松可调用。
连接生命周期
| 对象 | 何时拿到 | 关键字段 |
|---|---|---|
ConnectionRequest | 由 connect.oauth 返回,可经 getConnectionRequest / waitForConnection 重新读取 | id、status(initiated → connected / failed / expired)、authorizationUrl、connectedAccountId、externalUserId、connectionName、expiresAt |
ConnectedAccount | 由 connect.apiKey / connect.customCredential 同步返回;OAuth 请求完成后也指向它 | id / connectedAccountId、status(active、reauth_required、error、disconnected)、available、externalUserId、connectionName、service |
只有当执行所需条件都就绪时,available 才为 true:provider config 是 active、账号是 active、底层 app 是 active,且凭证存在。两个 status 字段都是开放联合;处理时保留默认分支,以兼容新的后端状态。
从 Composio / Pipedream 迁移?
| Composio / Pipedream | @oomol-lab/connector |
|---|---|
userId / external_user_id | externalUserId |
connectedAccounts.initiate / createConnectToken(OAuth) | project.connect.oauth |
connectedAccounts.initiate + AuthScheme.APIKey | project.connect.apiKey |
waitForConnection() | project.waitForConnection() |
tools.execute(slug, { userId, arguments }) | project.execute(externalUserId, actionId, input) |
composio.getEntity(userId) | project.forUser(externalUserId) |
参考
Connector(个人 api_… key)
new Connector(config: ClientConfig)
oomol.execute(actionId, input, options?) // → action 输出
oomol.executeRaw(actionId, input, options?) // → { data, executionId, actionId, message }
oomol.<service>.<action>(input, options?) // execute 的 namespace 语法糖
oomol.using(scope) // → 不可变的作用域子客户端
oomol.proxy(service, { endpoint, method, query?, headers?, body? }, options?) // → { status, headers, data }
oomol.catalog.action(actionId, options?) // → ActionMetadata
oomol.catalog.actions(service, options?) // → ActionMetadata[]
oomol.catalog.providers(query?, options?) // → ProviderMetadata[] query: { service?: string[]; q?: string }
oomol.apps.list(options?) // → ConnectedApp[]
ProjectConnector(项目 oo_proj_… key)
new ProjectConnector(config: ProjectConnectorConfig)
project.connect.oauth(externalUserId, input, options?) // → ConnectionRequest(待处理)
project.connect.apiKey(externalUserId, input, options?) // → ConnectedAccount(同步)
project.connect.customCredential(externalUserId, input, options?) // → ConnectedAccount(同步)
project.getConnectionRequest(connectionRequestId, options?) // → ConnectionRequest
project.waitForConnection(requestOrId, options?) // → ConnectionRequest options: { pollIntervalMs?, maxWaitMs?, signal?, timeoutMs? }
project.execute(externalUserId, actionId, input, options?) // → action 输出
project.executeRaw(externalUserId, actionId, input, options?) // → { data, executionId, actionId, message }
project.forUser(externalUserId) // → ProjectUser(方法相同,id 已绑定)
connect.* 的入参是 { service | providerConfigId } & { connectionName?, … }(service / providerConfigId 恰好取一个)。execute 的选项额外增加 { providerConfigId?, service?, connectedAccountId?, connectionName? }。
导出项
import {
Connector,
ProjectConnector,
ConnectorError,
isRetryable,
} from "@oomol-lab/connector";
import type {
ClientConfig, CallOptions, ScopeOptions, RawResult,
ProxyRequest, ProxyResponse, ProxyMethod,
CatalogApi, ActionMetadata, ProviderMetadata, ProviderQuery,
AppsApi, ConnectedApp,
ConnectorErrorCode,
ProjectConnectorConfig, ProjectCallOptions, ProjectExecuteOptions,
ConnectionRequest, ConnectedAccount, ProviderSelector,
OAuthConnectInput, ApiKeyConnectInput, CustomCredentialConnectInput,
} from "@oomol-lab/connector";
可运行且经过类型检查的示例位于仓库的 examples/ 目录。
许可证
MIT,见 connector-sdk 仓库。