• SDK
  • Skills
  • 文档
  • Apps
  • 价格
GitHub 开始使用
  • SDK
  • Skills
  • 文档
  • Apps
  • 价格
  • GitHub
  • 开始使用

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> 前缀。
Actionprovider 上的一个操作,标识为 "<service>.<action>"(例如 gmail.search_threads)。action 由网关提供,客户端负责调用。
Connection(连接)某个 provider 已授权、已存储的凭证。你从不直接接触 token;只用 connectionName 指明用哪一个连接。OAuth 和凭证生命周期是网关的职责。
Organization(组织)可选的租户作用域,以 x-oo-organization-name 头发送。

同一套词汇贯穿整个 SDK 接口:action id 始终是 "<service>.<action>",connectionName 用来选择已存储的连接;在项目客户端中,externalUserId 标识你的某个终端用户。

常见操作

想要……用说明
执行一个已建模的 actionexecute / executeRaw带类型的一行调用。executeRaw 还会返回 { executionId, actionId, message }。
调用尚未建模为 action 的 endpointproxy透传到上游 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> 发送。
baseUrlhttps://connector.oomol.com/v1仅支持手动覆盖;没有基于环境变量的自动切换。
organization—调用在哪个租户下运行。
connectionName—当某个 provider 有多个连接时,选用哪一个。单连接场景可作为客户端默认值;多连接场景建议按调用或用 using() 设置。
timeoutMs30_000单次请求超时(毫秒)。
maxRetries2对 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、方法和类型相互独立。

ConnectorProjectConnector
API key个人 api_…项目 oo_proj_…
作用对象你自己的连接你的终端用户的连接
如何标识用户—externalUserId(由你选定)
接口execute、proxy、catalog、apps、namespaceconnect.*、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 / providerapp_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
Proxyproxy_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 页面。

Connector 授权入口页,显示 my-saas 正在连接 Gmail 并即将跳转到 Gmail

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_idexternalUserId
connectedAccounts.initiate / createConnectToken(OAuth)project.connect.oauth
connectedAccounts.initiate + AuthScheme.APIKeyproject.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 仓库。

X Discord YouTube GitHub
Shaun 的微信二维码

直接联系 CEO

copyright © 2026 oomol contributors. 浙ICP备2023018874号-1

自动
中文
  • English
  • 中文

探索

  • Apps
  • Skills
  • 价格

支持

  • 支持
  • 文档
  • 品牌资产

公司

  • 关于
  • 服务条款
  • 隐私政策

选择你的 Cookie 偏好

我们会使用必要存储保持网站正常运行,也会使用可选存储来记住偏好并了解汇总后的站点使用情况。

Cookie 设置

隐私偏好

管理 OOMOL 可以使用哪些可选存储。你随时可以在页脚重新修改这些选择。