---
author: OOMOL
author_url: https://oomol.com/ru/about/
dateModified: 2026-09-10
title: Руководство по OOMOL Connector SDK
description: Используйте TypeScript SDK, чтобы быстро добавить возможности OOMOL
  Connector в backend продукта.
lang: ru
canonical_url: https://oomol.com/ru/docs/connector-sdk/
markdown_url: https://oomol.com/ru/docs/connector-sdk.md
---

# Руководство по OOMOL Connector SDK

[`@oomol-lab/connector`](https://github.com/oomol-lab/connector-sdk) — TypeScript-клиент для gateway OOMOL Connector. Используйте его, когда нужно быстро добавить интеграции с apps в backend продукта без самостоятельной поддержки connector infrastructure. Ваш код вызывает hosted gateway OOMOL через SDK; OAuth, обновление token и хранение credentials provider остаются на стороне gateway.

Package легкий и не имеет зависимостей; SDK собирает request и разбирает response. Для вызовов вроде `gmail.search_threads`, `slack.post_message` и `notion.append_block` не нужны codegen или CLI.

Основные темы:

- Как установить SDK, получить API key и выполнить первый вызов.
- Пять слов, описывающих всю модель: gateway, provider, action, connection, team.
- Два пути вызова: dynamic string и namespace, а также чтение execution metadata.
- Как включить точные типы для отдельных actions без codegen.
- Как проксировать еще не смоделированные endpoints, просматривать catalog и выводить список connected apps.
- Как использовать `ProjectConnector`, чтобы подключать аккаунты от имени ваших end users и выполнять actions для них.
- Как использовать `OpenConnector`, чтобы выполнять те же actions через open-source Connector runtime, который вы размещаете самостоятельно.
- Как устроены ошибки, какие из них можно повторять, и как задавать scope, timeout и отмену вызовов.

## Установка

```sh
npm install @oomol-lab/connector   # или: bun add / pnpm add / yarn add
```

Требуется Node ≥ 18 из-за встроенных `fetch` и `AbortController`. SDK поставляется только с `dist`, не имеет runtime dependencies и помечен как `sideEffects: false`, поэтому корректно tree-shake'ится. Он работает в Node, Bun, Deno, edge runtimes и browser-based apps со стандартным `fetch`; в браузере API key должен передавать host.

## Получение API key

Вам нужен **personal API key** OOMOL Connector вида `api_…`. Создайте его в OOMOL Console:

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

Задайте его как environment variable. В этом руководстве везде используется `OOMOL_API_KEY`. Gateway авторизует каждый request и получает ключ как `Authorization: Bearer <apiKey>`.

> Personal key `api_…` выполняет actions на **ваших собственных** connections. Чтобы подключать аккаунты для *ваших* end users, используйте отдельный **project** key (`oo_proj_…`) и client `ProjectConnector`; см. [Подключите аккаунты для ваших пользователей](#подключите-аккаунты-для-ваших-пользователей). Чтобы разместить runtime самостоятельно вместо hosted gateway, open-source server использует собственный optional runtime token (`oct_…`) и client `OpenConnector`; см. [Самостоятельно размещенный runtime](#самостоятельно-размещенный-runtime).

## Быстрый старт

Создайте client, затем вызовите action. Две формы ниже эквивалентны.

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

const oomol = new Connector({ apiKey: process.env.OOMOL_API_KEY! });

// Путь 1: dynamic string. Можно вызвать любой action id.
const { threads } = await oomol.execute("gmail.search_threads", { query: "from:boss" });

// Путь 2: namespace sugar. Внутри тот же вызов.
const result = await oomol.gmail.search_threads({ query: "is:unread" });
```

`execute` напрямую возвращает output action. Если также нужны execution metadata, используйте `executeRaw`:

```ts
const raw = await oomol.executeRaw("gmail.search_threads", { query: "from:ceo" });
raw.data;        // то же значение, которое возвращает execute()
raw.executionId; // execution id, назначенный server (полезно для support / корреляции logs)
raw.actionId;    // отраженный action id
raw.message;     // человекочитаемое message из success envelope
```

Основной call flow состоит из `execute` и `executeRaw`.

## Понятия

В модели SDK есть пять ключевых понятий. Authentication, credentials и provider calls обрабатывает gateway:

| Термин | Что это |
| --- | --- |
| **Gateway** | Hosted service OOMOL Connector, с которым общается этот client. Он хранит credentials, выполняет реальные provider calls и возвращает единый envelope. SDK **не** выполняет integration logic локально. |
| **Provider / service** | Сторонний API (`gmail`, `slack`, `github`, `notion`, …). Это prefix `<service>` в action id. |
| **Action** | Операция у provider, идентифицируемая как `"<service>.<action>"` (например, `gmail.search_threads`). Actions предоставляет gateway, а client их вызывает. |
| **Connection** | Сохраненный и уже авторизованный credential для provider. Вы не работаете с token напрямую; вы указываете нужную connection через `connectionName`. OAuth и lifecycle credentials — задача gateway. |
| **Team** | Optional tenant scope, отправляемый в header `x-oo-team-name`. |

Та же лексика используется во всем SDK surface: action id всегда имеет вид `"<service>.<action>"`, `connectionName` выбирает сохраненную connection, а в project client `externalUserId` идентифицирует одного из ваших end users.

## Частые операции

| Нужно… | Используйте | Примечания |
| --- | --- | --- |
| Выполнить смоделированную action | `execute` / `executeRaw` | Типизированный вызов в одну строку. `executeRaw` также возвращает `{ executionId, actionId, message }`. |
| Обратиться к endpoint, который еще не смоделирован как action | `proxy` | Passthrough к upstream API; credentials connection подставляет gateway. |
| Передать actions в LLM / построить dynamic forms | `catalog` | Runtime JSON Schema (2020-12) для любой action или provider. |
| Узнать, что подключено | `apps.list` | Read-only список connections, которые вы уже связали. |
| Дать *вашим* users подключить *их* аккаунты | `ProjectConnector` | Отдельный project-scoped client для подключения аккаунтов от имени ваших end users и выполнения actions для них. |

Набор провайдеров и операций определяется шлюзом. Общее количество провайдеров и операций доступно в [API публичного каталога OOMOL](https://connector.oomol.com/v1/catalog); также можно просмотреть [каталог приложений](/ru/apps/directory/). Получайте провайдеров настроенного шлюза во время выполнения через `oomol.catalog.providers()`. Доступные операции зависят от авторизации аккаунта и политики доступа.

## Точные типы (опционально)

Путь dynamic string компилируется для **любого** `actionId`. По умолчанию каждая action типизирована свободно (`Record<string, any>` на входе и выходе), чтобы новые actions можно было вызывать сразу. Для точных input/output types по отдельным actions и JSDoc установите companion types package и добавьте **один side-effect import на каждый provider**, который используете:

```ts
import { Connector } from "@oomol-lab/connector";
import "@oomol-lab/connector-types/gmail";   // точные types + JSDoc для gmail.*
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" }); // input + output теперь точные
await oomol.notion.append_block({ pageId, text });        // notion не импортирован → все еще свободный вызов
```

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

Без codegen, без generated files в commit, без CLI. Зарегистрированные actions получают literal completion и точные input/output types; **незарегистрированные переходят к `Record<string, any>`**. Если types package отстает от backend, новые actions все равно остаются вызываемыми через loose fallback. Core runtime не зависит от types package.

> Нужно установить `moduleResolution` в `bundler`, `node16` или `nodenext`, чтобы subpath imports (`@oomol-lab/connector-types/gmail`) разрешались. Подробности настройки см. в repository [`@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types).

## Конфигурация

Все fields, кроме `apiKey`, optional:

```ts
new Connector({
  apiKey: process.env.OOMOL_API_KEY!,        // required
  baseUrl: "https://connector.oomol.com/v1", // default
  team: "acme",                              // default team → x-oo-team-name
  connectionName: "work",                    // default connection (лучше задавать per-call / через using())
  timeoutMs: 30_000,                         // default per-request timeout
  maxRetries: 2,                             // default; retry для 429 / 5xx / network с backoff + jitter
  fetch: customFetch,                        // inject для tests / proxies / tracing
});
```

| Field | Default | Примечания |
| --- | --- | --- |
| `apiKey` | — | Обязателен. Отправляется как `Authorization: Bearer <apiKey>`. |
| `baseUrl` | `https://connector.oomol.com/v1` | Только ручное переопределение; автоматического переключения по env нет. |
| `team` | — | Tenant, в котором выполняется call. |
| `connectionName` | — | Какую stored connection использовать, если у provider их несколько. Подходит как client default для простых setups; при нескольких connections задавайте per call или через `using()`. |
| `timeoutMs` | `30_000` | Per-request timeout в миллисекундах. |
| `maxRetries` | `2` | Retry для 429 / 5xx / network errors с exponential backoff и jitter. |
| `fetch` | global `fetch` | Inject custom `fetch` для tests, proxy agents или tracing. |

### Scopes и параметры вызова

Три слоя разрешаются в таком приоритете: **per-call options > `using()` scope > client defaults.**

`using()` возвращает immutable scoped sub-client, объединяющий указанные defaults; исходный client не меняется:

```ts
const work = oomol.using({ connectionName: "work", team: "acme" });
await work.gmail.search_threads({ query: "label:urgent" }); // выполняется под "work" / "acme"
```

Per-call options действуют только на этот call и имеют наивысший приоритет:

```ts
await oomol.execute(
  "gmail.search_threads",
  { query: "from:ceo" },
  {
    team: "acme",           // override team для этого call
    connectionName: "alt",  // выбрать другую connection для этого call
    timeoutMs: 10_000,      // более жесткий timeout для этого call
    retries: 0,             // отключить retries для этого call
    signal: controller.signal, // передать AbortSignal
  },
);
```

`connectionName` разрешается по тем же слоям. По wire он передается как header `x-oo-connector-alias` (field gateway называется `alias`); SDK surface использует `connectionName`.

## Три клиента с первого взгляда

Package экспортирует три client: для personal connections, для end-user connections в project и для runtime, который вы размещаете самостоятельно. Они делят transport и error model; credentials, methods и types у них отдельные.

| | `Connector` | `ProjectConnector` | `OpenConnector` |
| --- | --- | --- | --- |
| Auth | personal key `api_…` | project key `oo_proj_…` | optional runtime token `oct_…` |
| Общается с | hosted gateway OOMOL | hosted gateway OOMOL | open-source runtime, который запускаете **вы** |
| Действует на | **ваши собственные** connections | connections **ваших end users** | **ваши собственные** connections на вашем server |
| Идентифицирует user | — | `externalUserId` (вы выбираете) | — |
| Surface | `execute`, `executeRaw`, `proxy`, `catalog`, `apps`, namespaces | `connect.*`, `waitForConnection`, `getUserProfile`, `execute`, `executeRaw`, `forUser` | `execute`, `executeRaw`, `proxy`, `catalog`, `apps`, `health`, namespaces |
| Для чего | вызывать providers, которые вы подключили | строить SaaS product, где каждый user связывает свои accounts | самостоятельно размещать весь runtime (localhost, Docker, ваша infra) |

Этот section начинается с personal `Connector`. `ProjectConnector` описан в [Подключите аккаунты для ваших пользователей](#подключите-аккаунты-для-ваших-пользователей), а `OpenConnector` — в [Самостоятельно размещенный runtime](#самостоятельно-размещенный-runtime).

## Proxy: вызов endpoint без action

Когда gateway еще не смоделировал endpoint как action, вызывайте его напрямую через `proxy`. Gateway все равно подставляет credentials connection; request и response сохраняют форму upstream API.

```ts
// Типизированный GET. ВАЖНО: field называется `endpoint`, НЕ `path`.
const repos = await oomol.proxy<Array<{ name: string }>>("github", {
  endpoint: "/user/repos",
  method: "GET",
  query: { per_page: 5, sort: "updated" },
});
repos.status;               // upstream HTTP status
repos.data.map((r) => r.name);

// POST с body и upstream headers (они идут provider, а не gateway).
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` принимает path (разрешается относительно base URL provider) или полный URL, что полезно для providers с regional hosts, например `https://eu.posthog.com/api/...`. `method` — один из `GET | POST | PUT | PATCH | DELETE`. Response имеет вид `{ status, headers, data }`. Proxy body строго проверяется на backend: неизвестные top-level keys отклоняются как `invalid_input`.

## Catalog: просмотр providers и actions

Catalog предоставляет read-only runtime metadata для dynamic forms, validation и LLM tool definitions. Input/output schemas используют JSON Schema (2020-12) и не зависят от compile-time types package.

```ts
// Список providers; можно сузить server-side.
const all = await oomol.catalog.providers();                       // все providers
const mailish = await oomol.catalog.providers({ q: "mail" });      // free-text search → ?q=
const some = await oomol.catalog.providers({ service: ["gmail", "slack"] }); // restrict → ?service=…

// Все actions одного service.
const actions = await oomol.catalog.actions("gmail");

// Полные metadata одной action, включая runtime JSON Schema.
const meta = await oomol.catalog.action("gmail.search_threads");
meta.name;          // human-readable name
meta.requiredScopes;// OAuth scopes, нужные action
meta.inputSchema;   // JSON Schema (2020-12) для input
meta.outputSchema;  // JSON Schema (2020-12) для output
```

Каждый provider содержит `{ service, displayName, iconUrl, homepageUrl, categories, authTypes }`.

## Apps: список подключенных аккаунтов

`apps.list()` возвращает read-only view connections, которые gateway уже хранит для вас. Создание и удаление connections выполняется в 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}`);
}

// Чтобы выбрать конкретную connection, передайте ее connectionName как per-call selector.
const work = apps.find((a) => a.connectionName === "work");
if (work) {
  await oomol.execute("gmail.search_threads", { query: "is:unread" }, { connectionName: "work" });
}
```

## Обработка ошибок

Failures выбрасывают типизированную `ConnectorError`. Отмена со стороны caller (прерванный `AbortSignal`) отклоняется стандартной `AbortError`, поэтому ее можно обрабатывать отдельно от gateway или transport errors.

```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;        // discriminable union, например "rate_limited", "credential_expired"
    err.status;      // HTTP status (0 для client-side / network errors)
    err.requestId;   // failure-correlation id
    err.actionId;    // если применимо
    err.executionId; // если применимо
    err.data;        // upstream response body, например при provider_error
    if (isRetryable(err)) {
      // 429 / 5xx / network / rate_limited / proxy_upstream_timeout / request_in_progress
    }
  } else {
    throw err; // не ConnectorError, например AbortError от caller cancellation; пробросить дальше
  }
}
```

`err.code` — **open** union: известные backend codes получают autocompletion, а новые backend codes все равно проходят как strings. При обработке оставляйте default branch. Частые codes по группам:

| Группа | Codes |
| --- | --- |
| Input / request | `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 / auth | `credential_expired`, `scope_missing`, `user_oauth_client_required` |
| Connection selection | `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 / concurrency | `rate_limited`, `request_in_progress`, `request_key_conflict`, `request_key_used` |
| Только client (status 0, request не отправлен или transport failure) | `client_invalid_request`, `client_timeout`, `client_network_error`, `client_wait_timeout` |

`isRetryable(err)` возвращает `true` для `rate_limited`, `proxy_upstream_timeout`, `request_in_progress`, HTTP 429, любого 5xx и transport failures (status 0). Он возвращает `false` для client validation errors (`client_invalid_request`) и лимита `waitForConnection` (`client_wait_timeout`); в этих случаях обычно нужно изменить call или waiting-flow.

### Отмена и тайм-ауты

Передайте `AbortSignal`, чтобы отменить вызов; задайте `timeoutMs`, чтобы ограничить один call. Встроенный retry layer обрабатывает transient failures. Используйте `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"
}
```

## Подключите аккаунты для ваших пользователей

`Connector` выполняет actions на **ваших собственных** connections. `ProjectConnector` предназначен для SaaS products: *ваши* end users подключают *свои* аккаунты Gmail / Slack / GitHub / … через ваше app, а ваш backend выполняет actions от их имени. Это managed-auth model, используемая в продуктах вроде [Composio](https://composio.dev) и [Pipedream Connect](https://pipedream.com/docs/connect).

В managed auth **credentials никогда не проходят через ваше application или какую-либо model**. User авторизуется на gateway-hosted page; gateway хранит credential и автоматически обновляет token. Ваш код хранит только opaque identifiers.

### Идентификатор конечного пользователя

**`externalUserId`** — ключ изоляции user для project client. Вы выбираете его сами, обычно из собственной user database. Project operations, такие как подключение account, ожидание connection и выполнение action, scoped к нему. Передавайте один и тот же `externalUserId` последовательно, и gateway будет изолировать connections каждого user.

### Создание проектного клиента

`ProjectConnector` — **отдельный client**, создаваемый с **project API key** (`oo_proj_…`). Он открывает только project-scoped operations и не включает surface personal client: `execute` / namespace / `proxy` / `catalog` / `apps`.

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

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

> **Сначала настройка в Console.** Перед тем как backend будет подключать accounts, administrator создает project, provider config и project API key в OOMOL Console. Однократная настройка и соответствующий backend REST flow описаны в [руководстве по OOMOL Connector SaaS](/ru/docs/connector-saas/). Этот SDK — типизированная обертка над тем же runtime API.

### OAuth: создайте ссылку, затем дождитесь завершения

OAuth состоит из двух шагов: создать pending connection request, отправить user на authorization, затем дождаться завершения.

```ts
// 1. Создайте pending connection request для одного из ваших users.
const request = await project.connect.oauth("user_42", {
  service: "gmail",
  connectionName: "work",                          // назначаемое имя; позже используйте его для выбора account
  returnUri: "https://app.example.com/connected",  // куда gateway вернет user после callback
});

// 2. Отправьте user на страницу authorization provider.
redirectUserTo(request.authorizationUrl);

// 3. Poll, пока user не завершит процесс (или он не завершится ошибкой / не истечет). Возвращает final connection request.
const connected = await project.waitForConnection(request);
connected.status;             // "connected" | "failed" | "expired"
connected.connectedAccountId; // id сохраненного account после подключения
```

`authorizationUrl` сначала открывает Connector-hosted entry page, где указаны ваш project и provider, который user собирается авторизовать, затем отправляет user на OAuth page provider.

![Страница входа авторизации Connector, где my-saas подключается к Gmail перед переходом в Gmail](/img/docs/connector-sdk/en/authorization-entry.webp)

`waitForConnection` poll'ит до тех пор, пока request не выйдет из состояния `initiated`, и возвращает его. Если user не завершит authorization, request естественно станет `expired`. Если раньше истечет `maxWaitMs` (по умолчанию `600_000` ms, соответствует expiry request), будет выброшена `ConnectorError` с code `client_wait_timeout`; прерванный `signal` отклонится стандартной `AbortError`.

Когда ваш `returnUri` вызывается, gateway добавляет query parameters, которые можно прочитать на callback page:

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

…или при cancellation / provider error:

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

### API key / custom credential: синхронно

Подключения через API key и custom credential сразу возвращают account. Только OAuth требует `waitForConnection`.

```ts
// Собственный upstream key end user (например, OpenAI sk-…), а не ваш oo_proj_ key.
const account = await project.connect.apiKey("user_42", { service: "openai", apiKey: "sk-..." });
account.available; // может ли account выполнять actions прямо сейчас

// Provider-specific credential fields, которые gateway валидирует по provider config.
await project.connect.customCredential("user_42", {
  service: "jira",
  values: { email: "user@acme.com", token: "..." },
});
```

Каждый вызов `connect.*` идентифицирует provider **ровно одним** из `service` или `providerConfigId`. Используйте `providerConfigId`, если в project несколько configs для одного service; `service` — простой случай.

### Под каким аккаунтом они подключились?

`getUserProfile` читает владельца стороннего аккаунта за connected account: кем ваш end user на самом деле является у provider. Gateway получает данные от provider вживую и нормализует их в одну форму для всех providers.

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

profile.id;          // стабильный user id на стороне provider
profile.kind;        // "user" | "bot" | "service_account" | "unknown" (открытое объединение)
profile.username;    // имя пользователя / handle или null
profile.displayName; // отображаемое имя или null
profile.avatarUrl;   // URL аватара или null
profile.email;       // email или null, если выданные scopes его не раскрывают
profile.metadata;    // специфичные для provider поля, которые раскрывает gateway
```

Поля, которые provider не раскрывает (или которые не покрыты выданными scopes, чаще всего `email`), возвращаются как `null`, а не отсутствуют. `fetchedAt` — это Unix-время в миллисекундах, когда gateway прочитал профиль. Передавайте `connectedAccountId` из результата `connect.apiKey` / `connect.customCredential` либо из `ConnectionRequest`, дошедшего до `connected`. Неизвестный id отклоняется с `connected_account_not_found`; provider без capability `userProfile` — с `profile_not_found`; inactive или unusable account может отклоняться с `app_not_ready`, `app_auth_type_mismatch` или `credential_expired`. У sub-client, ограниченного одним пользователем, метод тоже есть: `user.getUserProfile(connectedAccountId)`.

### Выполнение от имени пользователя

```ts
// Provider service выводится из prefix actionId ("gmail").
// Без connectionName / connectedAccountId используется latest active account этого user.
const out = await project.execute(
  "user_42",
  "gmail.search_threads",
  { query: "is:unread" },
  { connectionName: "work" },
);
```

Приоритет выбора account: `connectedAccountId` (конкретный account) выше `connectionName` (account по имени); если нет ни того ни другого, gateway использует latest active account этого user для данного provider. `project.executeRaw` возвращает такой же envelope `{ data, executionId, actionId, message }`, как personal client.

### Ограничение одним пользователем

`forUser` один раз привязывает `externalUserId`, чтобы в последующих calls не повторять 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` использует тот же registry [`@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types), что и personal path: импортированные providers получают точный input/output, остальные остаются вызываемыми свободно.

### Жизненный цикл connection

| Object | Когда вы его получаете | Key fields |
| --- | --- | --- |
| `ConnectionRequest` | возвращается `connect.oauth`, перечитывается через `getConnectionRequest` / `waitForConnection` | `id`, `status` (`initiated` → `connected` / `failed` / `expired`), `authorizationUrl`, `connectedAccountId`, `externalUserId`, `connectionName`, `expiresAt` |
| `ConnectedAccount` | возвращается синхронно `connect.apiKey` / `connect.customCredential`; на него указывает завершенный OAuth request | `id` / `connectedAccountId`, `status` (`active`, `reauth_required`, `error`, `disconnected`), `available`, `externalUserId`, `connectionName`, `service` |

`available` равен `true` только когда все нужное для execution готово: provider config active, account active, underlying app active, credential существует. Оба status fields — **open** unions; оставляйте default branch, чтобы обработать новые backend statuses.

### Переходите с 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)` |

## Самостоятельно размещенный runtime

Запускаете open-source Connector server самостоятельно — на localhost, в Docker или в собственной infra? **`OpenConnector`** — personal client для него. Он повторяет surface `Connector` (оба call paths, `proxy`, `catalog`, `apps`), но направлен на server, который запускаете *вы*, поэтому уже написанный code почти не меняется. Поднятие server — отдельная тема; см. [руководство по самостоятельному размещению OpenConnector](/ru/docs/openconnector-self-hosting/).

Open-source runtime — self-hostable counterpart **personal** продукта: один server, один user, actions выполняются на собственных connections этого user. Здесь нет `team` и нет модели end users — server ваш.

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

const open = new OpenConnector(); // default http://localhost:3000; fresh instance не требует auth

await open.execute("hackernews.get_top_stories", {});             // path 1 — dynamic string
await open.gmail.search_threads({ query: "from:boss" });          // path 2 — namespace sugar, те же registry types
await open.proxy("github", { endpoint: "/user", method: "GET" }); // path 3 — passthrough к еще не смоделированному endpoint
await open.catalog.search("send email", { limit: 5 });            // catalog extras: search, services
await open.apps.list();                                           // read-only view connections runtime
```

Оба call paths и workflow precise types идентичны personal `Connector`; применяются те же side-effect imports `@oomol-lab/connector-types`. `proxy` работает так же, с одним отличием runtime: `endpoint` должен быть **относительным path**, начинающимся с `/` — runtime отклоняет полные URL как `invalid_input`, тогда как hosted gateway принимает и полный URL. Providers без proxy executor отвечают `proxy_not_supported`.

### Укажите ваш сервер

`baseUrl` — это **origin** server, а не URL `/v1`: client сам добавляет path prefix. Auth — один **optional** runtime token (`oct_…`), созданный в web console runtime (tab Access). Fresh instance без tokens отвечает без token; как только tokens появляются, server начинает их требовать.

```ts
const open = new OpenConnector({
  baseUrl: "https://connect.internal.example.com",       // server ORIGIN — не /v1 url
  runtimeToken: process.env.OOMOL_CONNECT_RUNTIME_TOKEN, // oct_…; опустите, пока у instance нет tokens
  connectionName: "work",                                // optional client-level default connection
});
```

Каждый field optional. `timeoutMs`, `maxRetries` и `fetch` ведут себя точно так же, как в hosted client.

### Интерфейс только для runtime

`OpenConnector` добавляет несколько members, которых нет у hosted client, и убирает неприменимые:

```ts
await open.health();                                    // { ok, runtime } — проверка connectivity / auth
await open.catalog.services();                          // все service id, у которых есть actions
await open.catalog.search("top stories", { limit: 3 }); // ранжирует actions по free-text relevance
await open.apps.listByService("github");                // connections одного service
await open.apps.authenticated(["github", "notion"]);    // где сохранен REAL credential
```

Здесь **нет `using()`**. У выбора connection только два слоя: per-call `connectionName` переопределяет client-level default, а без обоих runtime возвращается к своей connection `"default"`.

> **Управление находится в web console, а не в SDK.** Создание connections, настройка OAuth clients и выпуск runtime tokens — server administration, намеренно вынесенное за пределы SDK. `OpenConnector` только *использует* то, что настроено в console; эта часть описана в [руководстве по самостоятельному размещению](/ru/docs/openconnector-self-hosting/). Как и в hosted client, service id, совпадающий с member name (`execute` / `executeRaw` / `health` / `proxy` / `catalog` / `apps`), продолжает работать через `execute("<service>.<action>", …)`; скрывается только его namespace sugar.

Полный runnable tour — [`examples/open.ts`](https://github.com/oomol-lab/connector-sdk/blob/main/examples/open.ts).

## Справочник

### `Connector` (личный ключ `api_…`)

```ts
new Connector(config: ClientConfig)

oomol.execute(actionId, input, options?)     // → output action
oomol.executeRaw(actionId, input, options?)  // → { data, executionId, actionId, message }
oomol.<service>.<action>(input, options?)    // namespace sugar для execute
oomol.using(scope)                           // → immutable scoped sub-client
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_…`)

```ts
new ProjectConnector(config: ProjectConnectorConfig)

project.connect.oauth(externalUserId, input, options?)            // → ConnectionRequest (pending)
project.connect.apiKey(externalUserId, input, options?)           // → ConnectedAccount (synchronous)
project.connect.customCredential(externalUserId, input, options?) // → ConnectedAccount (synchronous)
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?)        // → output action
project.executeRaw(externalUserId, actionId, input, options?)     // → { data, executionId, actionId, message }
project.forUser(externalUserId)                                   // → ProjectUser (те же methods, id привязан)
```

Input `connect.*` — `{ service | providerConfigId } & { connectionName?, … }` (ровно один из `service` / `providerConfigId`). Options `execute` добавляют `{ providerConfigId?, service?, connectedAccountId?, connectionName? }`.

### `OpenConnector` (самостоятельно размещенный runtime, опциональный token `oct_…`)

```ts
new OpenConnector(config?: OpenConnectorConfig)   // каждый field optional; baseUrl default — http://localhost:3000

open.execute(actionId, input, options?)       // → output action
open.executeRaw(actionId, input, options?)    // → { data, executionId, actionId, message }
open.<service>.<action>(input, options?)      // namespace sugar для execute
open.health(options?)                         // → { ok, runtime }
open.proxy(service, { endpoint, method, query?, headers?, body? }, options?) // → { status, headers, data }   (endpoint должен быть относительным path)
open.catalog.action(actionId, options?)       // → OpenActionMetadata
open.catalog.actions(service, options?)       // → OpenActionMetadata[]
open.catalog.services(options?)               // → string[]   (service ids, у которых есть actions)
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[]   (services с реальным credential)
```

`options` — `{ connectionName?, signal?, timeoutMs?, retries? }` (без `team`, без `using()`). `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";
```

Runnable и type-checked examples находятся в directory [`examples/`](https://github.com/oomol-lab/connector-sdk/tree/main/examples) repository.

## Лицензия

MIT, см. repository [connector-sdk](https://github.com/oomol-lab/connector-sdk).
