botruntime
Интеграции

Разработка и публикация интеграций

Соберите свою интеграцию: определение и бандл, публикация с проверкой sha256, каталог определений в воркспейсе, установка на бота и регистрация вебхука.

Интеграция подключает бота к внешнему сервису. Она даёт канал (входящие и исходящие сообщения), действия и события. Это отдельный TypeScript-проект с тем же контрактом исполнения, что и у бота: декларация плюс бандл. Платформа загружает бандл и запускает его в облаке.

На этой странице — как написать свою интеграцию, а не только поставить готовую из каталога.

Проект состоит из двух частей:

  • Определение — файл integration.definition.ts. Имя, версия, схема конфигурации, каналы, действия, события. Публичный «паспорт» интеграции.
  • Реализация — код в src/. Обработчики register/unregister, исходящие сообщения каналов и входящий handler вебхука.

brt build собирает проект в один самодостаточный бандл .botpress/dist/index.cjs. Публикация регистрирует определение в каталоге вашего воркспейса и загружает бандл. После этого интеграцию ставят на ботов — как первопартийную.

Создайте проект

Заведите проект из стартового шаблона:

Новый проект интеграции
brt init --type integration --template webhook-message --name my-channel
cd my-channel

Шаблоны: empty (пустой каркас), hello-world (одно действие), webhook-message (канал с входящим вебхуком). Минимальный набор файлов:

integration.definition.ts
package.json
index.ts

package.json объявляет имя проекта, скрипты brt и зависимость от SDK. Имя и версию самой интеграции задаёт определение (name/version в integration.definition.ts), а не package.json:

package.json
{
  "name": "my-channel",
  "type": "module",
  "scripts": {
    "build": "brt build",
    "publish": "brt integrations publish"
  },
  "dependencies": {
    "@holocronlab/botruntime-sdk": "^6.11.2"
  }
}

Определение

Опишите интерфейс интеграции в integration.definition.ts. Схему конфигурации задаёт zod. При публикации она превращается в JSON-схему каталога — по ней валидируется конфиг на установке.

integration.definition.ts
import { z, IntegrationDefinition } from '@holocronlab/botruntime-sdk'

export default new IntegrationDefinition({
  name: 'my-channel',
  version: '0.1.0',
  configuration: {
    schema: z.object({
      webhookUrl: z.string().describe('URL, куда постить ответы бота.'),
    }),
  },
  channels: {
    webhook: {
      messages: {
        text: { schema: z.object({ text: z.string() }) },
      },
      conversation: {
        tags: {
          id: { title: 'Conversation ID', description: 'ID диалога во внешнем сервисе' },
        },
      },
    },
  },
  user: {
    tags: {
      id: { title: 'User ID', description: 'ID пользователя во внешнем сервисе' },
    },
  },
})

Ключевые блоки:

БлокНазначение
configuration.schemaполя установки (URL, идентификаторы); секретные значения запечатываются при установке
channelsканалы диалога: какие типы сообщений интеграция принимает и отправляет
actionsвызываемые операции (input/output через zod) — доступны боту как инструменты
eventsименованные события, которые интеграция эмитит внутрь платформы
user.tags / conversation.tagsвнешние идентификаторы, по которым платформа склеивает диалоги и пользователей

Реализация

Реализация — это new Integration({...}) в src/index.ts. register и unregister вызываются при сохранении и снятии конфигурации: в них проверяют доступы и заводят или убирают ресурсы во внешнем сервисе. Исходящие сообщения канала уходят наружу. Входящий handler принимает вебхук и заводит сообщение в диалог.

src/index.ts
import * as sdk from '@holocronlab/botruntime-sdk'
import axios from 'axios'

export default new sdk.Integration({
  register: async ({ ctx }) => {
    // Проверьте конфигурацию и доступ к внешнему сервису.
    if (!ctx.configuration.webhookUrl) {
      throw new sdk.RuntimeError('webhookUrl обязателен')
    }
  },
  unregister: async () => {},
  actions: {},
  channels: {
    webhook: {
      messages: {
        text: async ({ ctx, conversation, payload }) => {
          await axios.post(ctx.configuration.webhookUrl, {
            conversationId: conversation.tags.id,
            text: payload.text,
          })
        },
      },
    },
  },
  handler: async ({ client, req }) => {
    if (!req.body) {
      return { status: 400, body: JSON.stringify({ error: 'No body' }) }
    }

    const parsed = sdk.z
      .object({ userId: sdk.z.string(), conversationId: sdk.z.string(), text: sdk.z.string() })
      .safeParse(JSON.parse(req.body))
    if (!parsed.success) {
      return { status: 400, body: JSON.stringify({ error: 'Invalid body' }) }
    }
    const { userId, conversationId, text } = parsed.data

    const { conversation } = await client.getOrCreateConversation({
      channel: 'webhook',
      tags: { id: conversationId },
    })
    const { user } = await client.getOrCreateUser({ tags: { id: userId } })
    await client.createMessage({
      type: 'text',
      conversationId: conversation.id,
      userId: user.id,
      payload: { text },
      tags: {},
    })

    return { status: 200, body: JSON.stringify({ ok: true }) }
  },
})

Соберите бандл

brt build упаковывает проект в один самодостаточный файл. SDK, клиент API и раннер лежат внутри — платформе не нужны зависимости проекта:

Сборка
brt build
# → .botpress/dist/index.cjs

Бандл экспортирует default (реализацию) и handler (входящий вебхук). Именно этот файл читает публикация. Платформа исполняет его в отдельном процессе.

Опубликуйте

brt integrations publish публикует integration-проект через тот же Botpress-shaped deploy-контракт, что и brt deploy. Метаданные Hub, actions, channels, events, attributes, network и bundle принадлежат одной integration- сущности и не могут попасть в разные каталожные записи. Бот для публикации не нужен: команда работает под profile PAT в скоупе воркспейса.

Публикация из проекта интеграции
brt integrations publish --dryRun
brt integrations publish

По шагам команда:

  1. читает полный integration.definition.ts и собирает bundle;
  2. добавляет handle воркспейса к имени: yookassa становится botruntime/yookassa;
  3. ищет свою версию в owned-реестре, а публичные зависимости — в Hub;
  4. создаёт или обновляет одну integration-сущность с visibility=public.

--dryRun вызывает те же серверные проверки через validate endpoint, но не пишет определение и bundle. Для приватной версии используйте обычный brt deploy: его visibility по умолчанию — private. Публичная публикация доступна платформенному воркспейсу; другой workspace получает явную ошибку, а не скрытый private deploy.

Флаги:

ФлагНазначение
--noBuildне пересобирать — использовать уже собранный .botpress/dist/index.cjs
--dryRunпроверить create/update без записи
--api-url <url>выбрать прокси или стенд для разработки платформы

Команда работает только из integration-проекта. Флаги --name, --version-number, --config-schema-file и --noBundle удалены: они обходили полное определение и позволяли опубликовать схему без метаданных или кода.

icon должен ссылаться на SVG — это тот же контракт, который проверяет Botpress CLI. PNG/ICO сначала преобразуйте или заверните в SVG-контейнер.

Owned-реестр и публичный Hub

Owned-реестр содержит только интеграции текущего воркспейса. Публичные версии других владельцев читаются через Hub. Такое разделение не даёт CLI принять чужую публичную карточку за свою и обновить неверный ID.

Метод и путьДействие
GET /v1/admin/integrationsсписок интеграций текущего воркспейса
POST /v1/admin/integrationsсоздать полное определение и bundle
PUT /v1/admin/integrations/{id}обновить свою версию
POST /v1/admin/integrations/validateпроверить создание без записи
PUT /v1/admin/integrations/{id}/validateпроверить обновление без записи
DELETE /v1/admin/integrations/{id}удалить свою версию; установка блокирует удаление с 409
GET /v1/admin/hub/integrationsсписок публичных интеграций всех воркспейсов
GET /v1/admin/hub/integrations/{name}/{version}получить публичную версию и её ownerWorkspace

Режимы inbound-аутентификации не взаимозаменяемы:

  • shared_secret — kernel проверяет общий секрет до запуска бандла;
  • provider_verified — для провайдера без подписи: platform-owned handler повторно читает объект через API провайдера до эмита события;
  • handler_verified — для first-party HTTP API со своей per-user auth на каждом route (Chat использует x-user-key/HS256 JWT). Kernel по-прежнему резолвит webhook и tenant, но не требует общего webhook secret.

handler_verified нельзя использовать как короткий путь для внешнего webhook: обычная пользовательская интеграция должна выбрать shared_secret.

Установите на бота

Определение из каталога ставится на конкретного бота. Публикация адресовала воркспейс — установка адресует бота. Привяжите проект к боту (brt link --bot-id <id>), затем поставьте интеграцию с её конфигом:

В исходном IntegrationDefinition имя остаётся одним локальным сегментом. Публичный каталог добавляет handle публикующего воркспейса: исходное name: 'yookassa' устанавливается как botruntime/yookassa@0.1.0. Bare-ref name@version остаётся допустимым для приватных определений.

Установка и регистрация
printf '%s' "$BOT_KEY" | brt link --bot-id "$BOT_ID" --key-stdin
printf '{"webhookUrl":"https://example.com/hook"}' \
  | brt integrations install my-channel@0.1.0 --config-stdin
export WEBHOOK_ID='wh_replace_from_install_output'
brt integrations register "$WEBHOOK_ID"

install возвращает webhookId (публичный) и, для стандартного режима, webhookSecret (показывается один раз). register регистрирует вебхук во внешнем сервисе на адрес …/hooks/{webhookId} с этим секретом. Входящий трафик платформа проверяет по нему.

У встроенных провайдеров без webhook-подписи определение может использовать webhookAuthMode: "provider_verified". Такой режим доступен только platform-owned каталогу: секрет не создаётся, а бандл обязан повторно запросить объект через API провайдера и сверить статус и значимые поля до эмита события. Одного доверия к телу webhook недостаточно.

Режим авторизации фиксируется при создании name@version. Его нельзя сменить редеплоем той же версии: сервер отклонит запрос до публикации бандла. Repoint инсталляции между версиями с разными режимами также запрещён — новую версию нужно установить заново, чтобы секрет и проверяющий код не разошлись.

Для agent-проекта install/register меняют состояние в Cloud и обновляют dependency snapshot, но не собирают и не загружают bot bundle. После регистрации выполните brt deploy --adk и только затем проверяйте production-канал. Для fresh agent target канонический порядок начинается с первого deploy, а не с ручного link: deploy → install/register → второй deploy → приёмочная проверка.

Секреты никогда не идут через аргументы командной строки — только через stdin или --config-file/--value-file. В режиме shared_secret webhookSecret выдаётся один раз при установке. Потеряли — снимите установку и поставьте заново.

Эндпоинты установки адресуют бота — заголовок x-bot-id:

Метод и путьДействие
POST /v1/admin/integrations/installпоставить интеграцию на бота; тело {name, version, alias?, config}{installationId, webhookId, webhookSecret}
POST /v1/admin/integrations/{webhookId}/registerзарегистрировать вебхук установки → {ok, webhookId, webhookUrl}

Реестр интеграций

Owned-реестр возвращает только интеграции текущего воркспейса. Публичные версии читаются через Hub:

Получить интеграцию по имени и версии
curl -sS https://botruntime.ru/v1/admin/integrations/my-channel/0.1.0 \
  -H "Authorization: Bearer $BRT_TOKEN"
Метод и путьДействие
GET /v1/admin/integrationsсписок своих интеграций
GET /v1/admin/integrations/{name}/{version}получить свою интеграцию по имени и версии
GET /v1/admin/integrations/{id}получить свою интеграцию по идентификатору
GET /v1/admin/hub/integrations/{name}/{version}получить публичную интеграцию

Те же чтения есть в CLI: brt integrations get <ref>, brt integrations list, brt integrations delete <ref>.

Дальше

On this page