---
author: OOMOL
author_url: https://oomol.com/zh-cn/about/
dateModified: 2026-09-10
title: OOMOL TypeScript SDK 参考
description: 查阅 Connector、ProjectConnector 与 OpenConnector 共用的配置、类型、错误和完整 API。
lang: zh-CN
canonical_url: https://oomol.com/zh-cn/docs/connector-sdk/
markdown_url: https://oomol.com/zh-cn/docs/connector-sdk.md
---

# OOMOL TypeScript SDK 参考

[`@oomol-lab/connector`](https://github.com/oomol-lab/connector-sdk) 包含三个 TypeScript client。请先按产品路径选择 client，再查阅本页的共享配置与 API 参考：

- [`Connector`](/zh-cn/docs/connector-client/) 通过托管网关调用你在 OOMOL 账号中连接的账号。
- [`ProjectConnector`](/zh-cn/docs/project-connector/) 为 SaaS 产品的终端用户连接并调用各自的账号。
- [`OpenConnector`](/zh-cn/docs/openconnector-sdk/) 调用由你运维的 OpenConnector runtime。

三个 client 共用传输行为、精确 action 类型和错误模型，但使用的 key、账号边界与可用方法不同。

这个包轻量、零依赖；SDK 直接构造请求并解析返回。`gmail.search_threads`、`slack.post_message`、`notion.append_block` 等 action 可以直接通过 TypeScript 调用。

## 安装

```sh
npm install @oomol-lab/connector   # 或：bun add / pnpm add / yarn add
```

需要 Node ≥ 18（依赖内置的 `fetch` 和 `AbortController`）。SDK 只发布 `dist`，零运行时依赖，且 `sideEffects: false`，可以干净地做 tree-shaking。请在 Node、Bun、Deno 或边缘 worker 等可信服务端环境中使用 `Connector` 和 `ProjectConnector`。浏览器应用应调用你的后端，不要把个人或 Project API key 打包进客户端代码。

## 三个客户端一览

| | `Connector` | `ProjectConnector` | `OpenConnector` |
| --- | --- | --- | --- |
| 鉴权 | 个人 `api_…` key | Project `oo_proj_…` key | 可选 runtime token `oct_…` |
| 对接 | OOMOL 托管网关 | OOMOL 托管网关 | 你运行的 OpenConnector runtime |
| 账号资源 | 个人或 Team 的 connections | external users 的 connected accounts | runtime 中的 connections |
| 用途 | 调用个人或 Team 已连接的账号 | 为 SaaS 产品用户连接并调用账号 | 调用自部署 runtime |
| 接入指南 | [Connector SDK](/zh-cn/docs/connector-client/) | [ProjectConnector](/zh-cn/docs/project-connector/) | [OpenConnector SDK](/zh-cn/docs/openconnector-sdk/) |

## `Connector`：调用个人或 Team connections

### 获取 API key

你需要一个 OOMOL Connector 的**个人 API key**，形如 `api_…`。在 OOMOL Console 创建：

<https://console.oomol.com/api-key>

把它设为环境变量；本指南统一使用 `OOMOL_API_KEY`。每个请求都会由网关授权，并以 `Authorization: Bearer <apiKey>` 发送。

> 个人 `api_…` key 在个人或 Team connection 上执行 action。代终端用户连接账号时，使用 Project key（`oo_proj_…`）和 `ProjectConnector`；见 [ProjectConnector 接入指南](/zh-cn/docs/project-connector/)。调用自部署 runtime 时，使用可选 runtime token（`oct_…`）和 `OpenConnector`；见 [OpenConnector SDK 指南](/zh-cn/docs/openconnector-sdk/)。

### 快速上手

构造客户端后即可调用 action。下面两种写法等价，可任选一种。

```ts
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`：

```ts
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` 组成。

### `Connector` 的概念

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 和凭证生命周期是网关的职责。 |
| **Team（团队）** | 可选的租户作用域，以 `x-oo-team-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 和操作的覆盖由网关提供。通过 [OOMOL 公开目录 API](https://connector.oomol.com/v1/catalog)查看目录中的 provider 和操作总量，或浏览[应用目录](/zh-cn/apps/directory/)。使用 `oomol.catalog.providers()` 在运行时发现所配置网关的 provider；实际可调用的操作取决于账号授权与访问策略。

### 精确类型（可选）

动态字符串路径对**任意** `actionId` 都能编译通过。默认情况下，每个 action 使用宽松类型（入参和出参都是 `Record<string, any>`），便于先完成调用。需要精确入/出参类型和 JSDoc 时，安装配套类型包，并为用到的**每个 provider 添加一行 side-effect import**：

```ts
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 → 仍可宽松调用
```

```sh
npm install -D @oomol-lab/connector-types
```

精确类型由 `@oomol-lab/connector-types` 和 side-effect import 提供，项目中不会生成额外文件。已注册的 action 会得到字面量补全和精确的入/出参类型；**未注册的会退化为 `Record<string, any>`**。即使类型包落后于后端，新 action 也可以继续通过宽松类型调用。核心运行时与类型包分别发布。

> 需要把 `moduleResolution` 设为 `bundler`、`node16` 或 `nodenext`，子路径 import（`@oomol-lab/connector-types/gmail`）才能解析。配置细节见 [`@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types) 仓库。

### 配置

除 `apiKey` 外每个字段都是可选的：

```ts
new Connector({
  apiKey: process.env.OOMOL_API_KEY!,        // 必填
  baseUrl: "https://connector.oomol.com/v1", // 默认值
  team: "acme",                              // 默认团队 → x-oo-team-name
  connectionName: "work",                    // 默认连接（更推荐按调用 / using() 设置）
  timeoutMs: 30_000,                         // 默认单次请求超时
  maxRetries: 2,                             // 默认；对 429 / 5xx / 网络错误用退避 + 抖动重试
  fetch: customFetch,                        // 用于测试 / 代理 / 链路追踪时注入
});
```

| 字段 | 默认值 | 说明 |
| --- | --- | --- |
| `apiKey` | — | 必填。以 `Authorization: Bearer <apiKey>` 发送。 |
| `baseUrl` | `https://connector.oomol.com/v1` | 通过 client 配置显式覆盖。 |
| `team` | — | 调用在哪个租户下运行。 |
| `connectionName` | — | 当某个 provider 有多个连接时，选用哪一个。单连接场景可作为客户端默认值；多连接场景建议按调用或用 `using()` 设置。 |
| `timeoutMs` | `30_000` | 单次请求超时（毫秒）。 |
| `maxRetries` | `2` | 对 429 / 5xx / 网络错误重试，采用指数退避 + 抖动。 |
| `fetch` | 全局 `fetch` | 为测试、代理 agent 或链路追踪注入自定义 `fetch`。 |

对于 `gmail.send_email`、`slack.post_message` 这类有副作用的 action，除非已经确认该 action 提供可用的幂等机制，否则应传入 `{ retries: 0 }`。网络错误可能发生在 provider 已接受请求之后，自动重试可能造成重复操作。

#### 作用域与按调用选项

三个层级按此优先级解析：**按调用选项 > `using()` 作用域 > 客户端默认值。**

`using()` 返回一个不可变的、合并了给定默认值的子客户端；原客户端不受影响：

```ts
const work = oomol.using({ connectionName: "work", team: "acme" });
await work.gmail.search_threads({ query: "label:urgent" }); // 在 "work" / "acme" 下运行
```

按调用选项只作用于本次调用，并具有最高优先级：

```ts
await oomol.execute(
  "gmail.search_threads",
  { query: "from:ceo" },
  {
    team: "acme",               // 本次调用覆盖团队
    connectionName: "alt",      // 本次调用选用另一个连接
    timeoutMs: 10_000,          // 本次调用用更紧的超时
    retries: 0,                 // 本次调用禁用重试
    signal: controller.signal,  // 传入 AbortSignal
  },
);
```

`connectionName` 按同样的层级解析。它在传输层以 `x-oo-connector-alias` 头携带（网关字段名为 `alias`），SDK 接口统一使用 `connectionName`。

### Proxy：直接调用上游 endpoint

`proxy` 可以直接访问上游 API endpoint。网关会注入所选 connection 的凭证，请求和响应保持上游 API 的原始形态。

```ts
// 带类型的 GET。请求路径使用 `endpoint` 字段。
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），与编译期类型包相互独立。

```ts
// 列出 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 中完成。

```ts
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`，可与网关或传输错误区分。

```ts
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`、`profile_not_found` |
| 凭证 / 鉴权 | `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`。

```ts
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"
}
```

## `ProjectConnector`：为产品用户连接账号

`Connector` 在**你自己的**连接上执行 action。`ProjectConnector` 面向 SaaS 产品：**你的**终端用户通过你的应用连接**他们自己的** Gmail / Slack / GitHub / …… 账号，然后由你的后端代他们执行 action。这属于托管鉴权（managed auth）模型，使用方式类似 [Composio](https://composio.dev) 和 [Pipedream Connect](https://pipedream.com/docs/connect)。

使用 OAuth 时，用户在网关托管页面上授权，provider token 保留在网关中，你的代码只持有不透明标识符。`project.connect.apiKey` 和 `project.connect.customCredential` 的边界不同：终端用户的密钥会先由你的可信后端接收，再发送给网关。不要让这些密钥进入浏览器、提示词、模型上下文或日志。

### 终端用户标识符

**`externalUserId`** 是项目客户端的用户隔离键，由**你**选定，通常使用你自己数据库里的用户 id。连接账号、等待连接、执行 action 等项目操作都按它作用域。始终传入一致的 `externalUserId`，网关会让每个用户的连接彼此隔离。

### 构造项目客户端

`ProjectConnector` 是一个使用 **Project API key**（`oo_proj_…`）的独立客户端。它提供 `connect.*`、`waitForConnection`、`getUserProfile`、`execute`、`executeRaw` 和 `forUser` 等 Project 作用域操作。

```ts
import { ProjectConnector } from "@oomol-lab/connector";

const project = new ProjectConnector({ apiKey: process.env.OOMOL_PROJECT_API_KEY! }); // oo_proj_...
```

> **先在 Console 完成配置。** 在你的后端连接账号之前，需要由管理员在 OOMOL Console 创建 Project、服务配置（provider config）和 Project API key。一次性配置和对应的后端 REST 流程见 [Connector for SaaS 使用指南](/zh-cn/docs/connector-saas/)。本 SDK 是同一套运行时 API 的带类型封装。

### OAuth：创建链接，再等待完成

OAuth 分两步：创建待处理的连接请求，引导用户授权，再等待完成。

```ts
// 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](/img/docs/connector-sdk/zh-cn/authorization-entry.webp)

`waitForConnection` 会轮询到请求离开 `initiated` 状态后返回它。如果用户未完成授权，请求会自然变为 `expired`。`maxWaitMs`（默认 `600_000` 毫秒，与请求过期时间一致）先耗尽时，会抛出错误码为 `client_wait_timeout` 的 `ConnectorError`；被中止的 `signal` 会抛出标准 `AbortError`。

当你的 `returnUri` 被命中时，网关会追加一些 query 参数，你可以在回调页面读取：

```text
status=success
service=gmail
providerConfigId=pc-1
externalUserId=user_42
connectedAccountId=ca-1
```

……或者在取消 / provider 出错时：

```text
status=error
code=<connector-error-code>
message=<human-readable-message>
```

### API key / 自定义凭证：同步返回

API key 和自定义凭证连接会立即返回账号；只有 OAuth 需要 `waitForConnection`。

```ts
// 终端用户自己的上游 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`。

### 用户连接的是哪个第三方账号

`getUserProfile` 读取一个已连接账号背后的第三方账号主体：你的终端用户在 provider 上到底是谁。网关会实时从 provider 拉取，并归一化成跨 provider 统一的结构。

```ts
const { service, profile, fetchedAt } = await project.getUserProfile(account.connectedAccountId);

profile.id;          // provider 侧稳定的用户 id
profile.kind;        // "user" | "bot" | "service_account" | "unknown"（开放联合类型）
profile.username;    // 用户名/handle，或 null
profile.displayName; // 展示名，或 null
profile.avatarUrl;   // 头像 URL，或 null
profile.email;       // 邮箱；已授权的 scope 不包含时为 null
profile.metadata;    // 网关暴露的 provider 特有字段
```

provider 未提供或已授权 scope 未覆盖的字段（最常见的是 `email`）会保留在响应中并设为 `null`。`fetchedAt` 是网关读取该资料时的 Unix 毫秒时间戳。`connectedAccountId` 可以来自 `connect.apiKey` / `connect.customCredential` 的返回值，或来自已进入 `connected` 状态的 `ConnectionRequest`。未知 id 会以 `connected_account_not_found` 拒绝；provider 缺少 `userProfile` 能力时返回 `profile_not_found`；账号未就绪或不可用时还可能返回 `app_not_ready`、`app_auth_type_mismatch` 或 `credential_expired`。限定到单个用户的子客户端上也有这个方法：`user.getUserProfile(connectedAccountId)`。

### 代用户执行 action

```ts
// 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：

```ts
const user = project.forUser("user_42");
const request = await user.connect.oauth({ service: "slack" });
const slack = await user.waitForConnection(request);
if (slack.status === "connected") {
  await user.execute(
    "slack.post_message",
    { channel: "#general", text: "shipped" },
    { connectedAccountId: slack.connectedAccountId },
  );
}
```

`project.execute` 复用与个人路径相同的 [`@oomol-lab/connector-types`](https://github.com/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)` |

## `OpenConnector`：调用自部署 runtime

想自己运行开源的 Connector 服务——在 localhost、Docker，或你自己的基础设施上？**`OpenConnector`** 就是它的个人客户端。它把 `Connector` 的接口（两条调用路径、`proxy`、`catalog`、`apps`）指向**你自己**运行的服务器，所以你已经写好的代码几乎不用改。搭建服务器是另一个话题，见 [OpenConnector 自托管指南](/zh-cn/docs/openconnector-self-hosting/)。

`OpenConnector` client 指向一个由你运维的 runtime。账号组织方式、connection 选择和访问策略由该 runtime 的配置决定。

```ts
import { OpenConnector } from "@oomol-lab/connector";

const open = new OpenConnector(); // 仅用于本地或私有网络；默认 http://localhost:3000

await open.execute("hackernews.get_top_stories", {});             // 路径 1 — 动态字符串
await open.gmail.search_threads({ query: "from:boss" });          // 路径 2 — namespace 语法糖，注册表类型相同
await open.proxy("github", { endpoint: "/user", method: "GET" }); // 路径 3 — 透传到尚未建模的 endpoint
await open.catalog.search("send email", { limit: 5 });            // catalog 扩展：search、services
await open.apps.list();                                           // 只读查看运行时的连接
```

两条调用路径和精确类型工作流都与个人 `Connector` 一致；同样的 `@oomol-lab/connector-types` side-effect import 也适用。OpenConnector 的 `proxy.endpoint` 接受以 `/` 开头的**相对路径**；传入绝对 URL 会返回 `invalid_input`。缺少 proxy executor 的 provider 会返回 `proxy_not_supported`。

### 指向你的服务器

`baseUrl` 接收服务器 **origin**，客户端会自动添加 API 路径前缀。未创建 token 的 runtime 只适合 localhost 或其他私有网络。通过公开 URL 暴露前，应先在 Web Console 的 Access 页面创建 runtime token（`oct_…`），并要求所有 `/v1` 和 `/mcp` client 携带它。

```ts
const open = new OpenConnector({
  baseUrl: "https://connect.internal.example.com",       // 服务器 origin
  runtimeToken: process.env.OOMOL_CONNECT_RUNTIME_TOKEN!, // oct_…；公开 runtime 必填
  connectionName: "work",                                // 可选的客户端级默认连接
});
```

每个字段都是可选的。`timeoutMs`、`maxRetries`、`fetch` 的行为与托管客户端完全一致。

### 仅运行时才有的接口

`OpenConnector` 提供以下 runtime 专用接口：

```ts
await open.health();                                    // { ok, runtime } — 连通性 / 鉴权探测
await open.catalog.services();                          // 所有有 action 的 service id
await open.catalog.search("top stories", { limit: 3 }); // 按全文相关度对 action 排序
await open.apps.listByService("github");                // 某个 service 的连接
await open.apps.authenticated(["github", "notion"]);    // 哪些已存有**真实**凭证
```

连接选择分两层：按调用的 `connectionName` 覆盖客户端默认值；两层都省略时，runtime 使用 `"default"` connection。

> **管理操作使用 Web Console。** 在控制台中创建 connection、配置 OAuth client 并生成 runtime token；`OpenConnector` SDK 调用已经配置好的 runtime。配置方法见[自托管指南](/zh-cn/docs/openconnector-self-hosting/)。当 service id 与成员名（`execute` / `executeRaw` / `health` / `proxy` / `catalog` / `apps`）冲突时，可以通过 `execute("<service>.<action>", …)` 调用。

完整可运行示例——[`examples/open.ts`](https://github.com/oomol-lab/connector-sdk/blob/main/examples/open.ts)。

## 参考

### `Connector`（个人 `api_…` key）

```ts
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）

```ts
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.getUserProfile(connectedAccountId, options?)              // → ConnectedAccountProfile
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? }`。

### `OpenConnector`（自托管运行时，可选 `oct_…` token）

```ts
new OpenConnector(config?: OpenConnectorConfig)   // 每个字段都可选；baseUrl 默认 http://localhost:3000

open.execute(actionId, input, options?)       // → action 输出
open.executeRaw(actionId, input, options?)    // → { data, executionId, actionId, message }
open.<service>.<action>(input, options?)      // execute 的 namespace 语法糖
open.health(options?)                         // → { ok, runtime }
open.proxy(service, { endpoint, method, query?, headers?, body? }, options?) // → { status, headers, data }（endpoint 必须是相对路径）
open.catalog.action(actionId, options?)       // → OpenActionMetadata
open.catalog.actions(service, options?)       // → OpenActionMetadata[]
open.catalog.services(options?)               // → string[]（有 action 的 service id）
open.catalog.providers(query?, options?)      // → ProviderMetadata[]
open.catalog.search(q, query?, options?)      // → OpenActionSearchResult[]   query: { service?, limit? }
open.apps.list(options?)                      // → ConnectedApp[]
open.apps.listByService(service, options?)    // → ConnectedApp[]
open.apps.authenticated(services, options?)   // → string[]（存有真实凭证的 service）
```

`options` 是 `{ connectionName?, signal?, timeoutMs?, retries? }`。`config` 支持 `{ baseUrl?, runtimeToken?, connectionName?, timeoutMs?, maxRetries?, fetch? }`。

### 导出项

```ts
import {
  Connector,
  ProjectConnector,
  OpenConnector,
  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,
  ConnectedAccountProfile, ProviderUserProfile, ProviderUserKind,
  OAuthConnectInput, ApiKeyConnectInput, CustomCredentialConnectInput,
  OpenConnectorConfig, OpenConnectorApi, OpenCallOptions, OpenExecuteOptions,
  OpenCatalogApi, OpenAppsApi, OpenHealth,
  OpenActionMetadata, OpenActionFollowUp, OpenActionAsyncLifecycle,
  OpenActionSearchResult, OpenSearchQuery,
} from "@oomol-lab/connector";
```

可运行且经过类型检查的示例位于仓库的 [`examples/`](https://github.com/oomol-lab/connector-sdk/tree/main/examples) 目录。

## 许可证

MIT，见 [connector-sdk](https://github.com/oomol-lab/connector-sdk) 仓库。
