🏠 Главная › developer guide › adding platform adapters
Добавление адаптера платформы
This guide covers adding a new messaging platform to the Hermes gateway. Адаптер платформы подключает Hermes к внешней службе обмена сообщениями (Telegram, Discord, WeCom и т. д.), чтобы пользователи могли взаимодействовать с агентом через эту службу.:::совет
Есть два способа добавить платформу:
- Плагин (рекомендуется для сообщества/сторонних разработчиков): поместите каталог плагина в ~/.hermes/plugins/ — никаких изменений основного кода не требуется. См. [Путь к плагину] (#plugin-path-recommended) ниже.
- Встроенный: изменяйте более 20 файлов в коде, конфигурации и документации. Используйте Встроенный контрольный список ниже.
Входящие сообщения принимаются адаптером и пересылаются через self.handle_message(event), который базовый класс направляет бегуну шлюза.
Путь к плагину (рекомендуется)
Система плагинов позволяет добавлять адаптер платформы без изменения какого-либо основного кода Hermes. Ваш плагин представляет собой каталог с двумя файлами:
Метаданные плагина. Блоки requires_env и optional_env автоматически заполняют записи пользовательского интерфейса hermes config (см. Surfacing Env Vars ниже).
name:my-platformlabel:My Platformkind:platformversion:1.0.0description:My custom messaging platform adapterauthor:Your Namerequires_env:-MY_PLATFORM_TOKEN# bare string works-name:MY_PLATFORM_CHANNEL# or rich dict for better UXdescription:"Channeltojoin"prompt:"Channel"password:falseoptional_env:-name:MY_PLATFORM_HOME_CHANNELdescription:"Defaultchannelforcrondelivery"password:false
адаптер.py
importosfromgateway.platforms.baseimport(BasePlatformAdapter,SendResult,MessageEvent,MessageType,)fromgateway.configimportPlatform,PlatformConfigclassMyPlatformAdapter(BasePlatformAdapter):def__init__(self,config:PlatformConfig):super().__init__(config,Platform("my_platform"))extra=config.extraor{}self.token=os.getenv("MY_PLATFORM_TOKEN")orextra.get("token","")asyncdefconnect(self,*,is_reconnect:bool=False)->bool:# Connect to the platform API, start listenersself._mark_connected()returnTrueasyncdefdisconnect(self)->None:self._mark_disconnected()asyncdefsend(self,chat_id,content,reply_to=None,metadata=None):# Send message via platform APIreturnSendResult(success=True,message_id="...")asyncdefget_chat_info(self,chat_id):return{"name":chat_id,"type":"dm"}defcheck_requirements()->bool:returnbool(os.getenv("MY_PLATFORM_TOKEN"))defvalidate_config(config)->bool:extra=getattr(config,"extra",{})or{}returnbool(os.getenv("MY_PLATFORM_TOKEN")orextra.get("token"))def_env_enablement()->dict|None:token=os.getenv("MY_PLATFORM_TOKEN","").strip()channel=os.getenv("MY_PLATFORM_CHANNEL","").strip()ifnot(tokenandchannel):returnNoneseed={"token":token,"channel":channel}home=os.getenv("MY_PLATFORM_HOME_CHANNEL")ifhome:seed["home_channel"]={"chat_id":home,"name":"Home"}returnseeddefregister(ctx):"""Plugin entry point — called by the Hermes plugin system."""ctx.register_platform(name="my_platform",label="My Platform",adapter_factory=lambdacfg:MyPlatformAdapter(cfg),# PASSIVE probe — "are deps/config present right now?". Called from# status displays and config loading, so it must NEVER pip-install.check_fn=check_requirements,# ACTIVE installer (optional) — only for platforms with a# lazy-installable SDK. create_adapter() calls it when check_fn# returns False, right before the gateway connects the platform.# Typically wraps tools.lazy_deps.ensure_and_bind(...). Omit it# and a False check_fn is a hard block.# ensure_deps_fn=ensure_requirements,validate_config=validate_config,required_env=["MY_PLATFORM_TOKEN"],install_hint="pip install my-platform-sdk",# Env-driven auto-configuration — seeds PlatformConfig.extra from# env vars before adapter construction. See "Env-Driven Auto-# Configuration" section below.env_enablement_fn=_env_enablement,# Cron home-channel delivery support. Lets deliver=my_platform cron# jobs route without editing cron/scheduler.py. See "Cron Delivery"# section below.cron_deliver_env_var="MY_PLATFORM_HOME_CHANNEL",# Per-platform user authorization env varsallowed_users_env="MY_PLATFORM_ALLOWED_USERS",allow_all_env="MY_PLATFORM_ALLOW_ALL_USERS",# Message length limit for smart chunking (0 = no limit)max_message_length=4000,# LLM guidance injected into system promptplatform_hint=("You are chatting via My Platform. ""It supports markdown formatting."),# Displayemoji="💬",)# Optional: register platform-specific toolsctx.register_tool(name="my_platform_search",toolset="my_platform",schema={...},handler=my_search_handler,)
Или через переменные среды (которые адаптер читает в __init__).
Что система плагинов обрабатывает автоматически
Когда вы вызываете ctx.register_platform(), за вас обрабатываются следующие точки интеграции — никаких изменений основного кода не требуется:
Точка интеграции
Как это работает
Создание адаптера шлюза
Registry checked before built-in if/elif chain
Разбор конфига
Platform._missing_() принимает любое имя платформы
Проверка подключенной платформы
Реестр validate_config() называется
Авторизация пользователя
allowed_users_env / allow_all_env отмечено
Автоматическое включение только для Env
env_enablement_fn использует PlatformConfig.extra + home_channel
Конфигурационный мост YAML
apply_yaml_config_fn переводит ключи config.yaml в переменные/дополнительные параметры окружения
Доставка Крон
cron_deliver_env_var заставляет deliver=<name> работать
hermes config Записи пользовательского интерфейса
requires_env / optional_env в plugin.yaml автоматически заполняется
механизм отправки (tools/send_message_tool.py)
Маршруты через адаптер живого шлюза
Кроссплатформенная доставка Webhook
Реестр проверен на наличие известных платформ
доступ к команде /update
флаг allow_update_command
Каталог каналов
Платформы плагинов, включенные в список
Подсказки системы
platform_hint внедрен в контекст LLM
Разбиение сообщений
max_message_length для умного разделения
Редактирование личных данных
флаг pii_safe
статус Гермеса
Показывает платформы плагинов с тегом (plugin)
настройка шлюза Гермес
Платформы плагинов появляются в меню настроек
инструменты Гермеса / навыки Гермеса
Платформы плагинов в конфигурации каждой платформы
Токен-блокировка (многопрофильная)
Используйте acquire_scoped_lock() в connect()
Orphaned config warning
Описательный журнал при отсутствии плагина
Автоконфигурация на основе Env
Большинство пользователей настраивают платформу, добавляя переменные env в ~/.hermes/.env, а не редактируя config.yaml. Хук env_enablement_fn позволяет вашему плагину выбирать эти переменные env до создания адаптера, поэтому статус шлюза Hermes, get_connected_platforms() и доставка cron видят правильное состояние без создания экземпляра SDK платформы.
def_env_enablement()->dict|None:"""Seed PlatformConfig.extra from env vars. Called by the platform registry during load_gateway_config(). Return None when the platform isn't minimally configured — the caller then skips auto-enabling. Return a dict to seed extras. The special 'home_channel' key is extracted and becomes a proper HomeChannel dataclass on the PlatformConfig; every other key is merged into PlatformConfig.extra. """token=os.getenv("MY_PLATFORM_TOKEN","").strip()channel=os.getenv("MY_PLATFORM_CHANNEL","").strip()ifnot(tokenandchannel):returnNoneseed={"token":token,"channel":channel}home=os.getenv("MY_PLATFORM_HOME_CHANNEL")ifhome:seed["home_channel"]={"chat_id":home,"name":os.getenv("MY_PLATFORM_HOME_CHANNEL_NAME","Home"),}returnseeddefregister(ctx):ctx.register_platform(name="my_platform",label="My Platform",adapter_factory=lambdacfg:MyPlatformAdapter(cfg),check_fn=check_requirements,validate_config=validate_config,env_enablement_fn=_env_enablement,#... other fields)
YAML→env Config Bridge
Некоторые пользователи предпочитают устанавливать ключи config.yaml (my_platform.require_mention, my_platform.allowed_channels и т. д.) вместо переменных env. Хук apply_yaml_config_fn позволяет вашему плагину владеть этим переводом вместо того, чтобы заставлять ядро gateway/config.py знать схему YAML вашей платформы.
importosdef_apply_yaml_config(yaml_cfg:dict,platform_cfg:dict)->dict|None:"""Translate config.yaml `my_platform:` keys into env vars / extras. yaml_cfg — the full top-level parsed config.yaml dict platform_cfg — the platform's own sub-dict (yaml_cfg.get("my_platform", {})) May mutate os.environ directly (use `not os.getenv(...)` guards to preserve env > YAML precedence) and/or return a dict to merge into PlatformConfig.extra. Return None or {} for no extras. """if"require_mention"inplatform_cfgandnotos.getenv("MY_PLATFORM_REQUIRE_MENTION"):os.environ["MY_PLATFORM_REQUIRE_MENTION"]=str(platform_cfg["require_mention"]).lower()allowed=platform_cfg.get("allowed_channels")ifallowedisnotNoneandnotos.getenv("MY_PLATFORM_ALLOWED_CHANNELS"):ifisinstance(allowed,list):allowed=",".join(str(v)forvinallowed)os.environ["MY_PLATFORM_ALLOWED_CHANNELS"]=str(allowed)returnNone# nothing extra to merge into PlatformConfig.extradefregister(ctx):ctx.register_platform(name="my_platform",...,apply_yaml_config_fn=_apply_yaml_config,)
Перехват вызывается во время load_gateway_config() после общего цикла общего ключа (который обрабатывает общие ключи, такие как unauthorized_dm_behavior, notice_delivery, reply_prefix, require_mention и т. д.) и перед _apply_env_overrides(), поэтому вашему плагину нужно только соединить ключи, специфичные для платформы.
Исключения, вызванные перехватчиком, поглощаются и протоколируются на уровне отладки — плагин, работающий некорректно, никогда не прерывает загрузку конфигурации шлюза.
Доставка Cron
Чтобы задания cron deliver=my_platform направлялись на настроенный домашний канал, установите для cron_deliver_env_var имя переменной env, которая содержит идентификатор чата/комнаты/канала по умолчанию:
Планировщик считывает эту переменную окружения при разрешении домашней цели для заданий deliver=my_platform, а также рассматривает платформу как допустимую цель cron при проверках в стиле _KNOWN_DELIVERY_PLATFORMS. Если ваш env_enablement_fn отправляет dict home_channel (см. выше), он имеет приоритет — cron_deliver_env_var является запасным вариантом для заданий cron, которые выполняются до заполнения env.
Доставка cron вне процесса
cron_deliver_env_var делает вашу платформу признанной целью deliver=. Чтобы фактическая отправка прошла успешно, когда задание cron выполняется в отдельном от шлюза процессе (т. е. hermes cron run отдельно от hermes шлюза), зарегистрируйте standalone_sender_fn:
asyncdef_standalone_send(pconfig,chat_id,message,*,thread_id=None,media_files=None,force_document=False,):"""Open an ephemeral connection / acquire a fresh token, send, and close."""#... open connection, send message, return result...return{"success":True,"message_id":"..."}# or {"error": "..."}ctx.register_platform(name="my_platform",...cron_deliver_env_var="MY_PLATFORM_HOME_CHANNEL",standalone_sender_fn=_standalone_send,)
Почему этот хук необходим: встроенные платформы (Telegram, Discord, Slack и т. д.) поставляют прямые помощники REST в tools/send_message_tool.py, поэтому cron может доставлять сообщения, не удерживая шлюз в одном и том же процессе. Платформы плагинов исторически зависели от _gateway_runner_ref(), который возвращает None вне процесса шлюза, поэтому без standalone_sender_fn отправка на стороне cron завершается неудачей с сообщением Нет действующего адаптера для платформы '<name>'.
Функция получает те же pconfig и chat_id, что и живой адаптер, плюс дополнительные ключевые слова thread_id, media_files и force_document. Возврат {"success": True, "message_id":...} рассматривается как успешная доставка; возврат {"error": "..."} отображает сообщение в delivery_errors cron. Исключения, возникающие внутри функции, перехватываются диспетчером и сообщаются как Ошибка автономной отправки подключаемого модуля: <причина>. Эталонные реализации находятся в plugins/platforms/{irc,teams,google_chat}/adapter.py.
Появление переменных Env в hermes config
hermes_cli/config.py сканирует plugins/platforms/*/plugin.yaml во время импорта и автоматически заполняет OPTIONAL_ENV_VARS из блоков requires_env и (необязательно) optional_env. Используйте форму rich-dict для предоставления правильных описаний, подсказок, флагов паролей и URL-адресов — пользовательский интерфейс настройки CLI подберет их бесплатно.
# plugins/platforms/my_platform/plugin.yamlname:my_platform-platformlabel:My Platformkind:platformversion:1.0.0description:>My Platform gateway adapter for Hermes Agent.author:Your Namerequires_env:-name:MY_PLATFORM_TOKENdescription:"BotAPItokenfromtheMyPlatformconsole"prompt:"MyPlatformbottoken"url:"https://my-platform.example.com/bots"password:true-name:MY_PLATFORM_CHANNELdescription:"Channeltojoin(e.g.#hermes)"prompt:"Channel"password:falseoptional_env:-name:MY_PLATFORM_HOME_CHANNELdescription:"Defaultchannelforcrondelivery(defaultstoMY_PLATFORM_CHANNEL)"prompt:"Homechannel(orempty)"password:false-name:MY_PLATFORM_ALLOWED_USERSdescription:"Comma-separateduserIDsallowedtotalktothebot"prompt:"Allowedusers(comma-separated)"password:false
Поддерживаемые ключи dict:name (обязательно), description, prompt, url, password (bool; автоматически определяется из *_TOKEN / *_SECRET / *_KEY / *_PASSWORD / *_JSON суффиксом, если он опущен), category (по умолчанию "messaging").
Записи с чистой строкой (- MY_PLATFORM_TOKEN) по-прежнему работают — они получают общее описание, автоматически полученное из label плагина. Если жестко запрограммированная запись для той же переменной уже существует в OPTIONAL_ENV_VARS, она выигрывает (обратная совместимость); форма плагина.yaml действует как запасной вариант.
UX Slow-LLM для конкретной платформы
Некоторые платформы имеют ограничения, которые меняют способ представления медленного ответа LLM:
LINE выдает одноразовый токен ответа, срок действия которого истекает примерно через 60 секунд после входящего события. Ответ с помощью этого токена бесплатен; возврат к дозированному API-интерфейсу Push невозможен. Если LLM не завершен к установленному сроку, можно выбрать «сжечь оплаченную квоту Push» или «сделать что-нибудь умнее с токеном ответа до истечения срока его действия».
WhatsApp помечает сеанс как неактивный через 24 часа, после чего принимаются только шаблонные сообщения.
В SMS нет концепции индикаторов ввода или прогрессивных обновлений — длинные ответы просто выглядят так, будто бот не в сети.
Это реальные ограничения, которые базовый BasePlatformAdapter не может предвидеть. Поверхность плагина намеренно оставляет место для адаптера, позволяющего накладывать специфичный для платформы UX поверх базового цикла набора текста без расширения списка kwarg.
Шаблон: подкласс _keep_typing для слоя промежуточного UX
BasePlatformAdapter._keep_typing — это тактовый сигнал индикатора набора текста — он запускается как фоновая задача во время генерации LLM и отменяется при доставке ответа. Чтобы наложить поведение, специфичное для платформы, на пороговое значение (например, отправить пузырь «все еще думает» через 45 секунд), переопределите _keep_typing в своем адаптере, запланируйте свою собственную задачу вместе с super()._keep_typing() и уничтожьте ее в finally:
classLineAdapter(BasePlatformAdapter):asyncdef_keep_typing(self,chat_id:str,*args,**kwargs)->None:ifself.slow_response_threshold<=0:awaitsuper()._keep_typing(chat_id,*args,**kwargs)returnasyncdef_fire_at_threshold()->None:try:awaitasyncio.sleep(self.slow_response_threshold)exceptasyncio.CancelledError:raise# Platform-specific work here — for LINE, send a Template# Buttons "Get answer" bubble using the cached reply token# so the user can fetch the cached response later via a# fresh (free) reply token from the postback callback.awaitself._send_slow_response_button(chat_id)side_task=asyncio.create_task(_fire_at_threshold())try:awaitsuper()._keep_typing(chat_id,*args,**kwargs)finally:ifnotside_task.done():side_task.cancel()try:awaitside_taskexcept(asyncio.CancelledError,Exception):pass
Ключевые моменты:
Всегда await super()._keep_typing(...). Сердцебиение при наборе полезно независимо — не заменяйте его, наложите поверх него.
Удалить побочную задачу в finally. Когда LLM завершает работу (или /stop отменяет выполнение), шлюз отменяет задачу ввода. Ваша побочная задача также должна учитывать эту отмену, иначе она задержится и может сработать после того, как ответ уже был доставлен.
Соединитесь с interrupt_session_activity, чтобы разрешить любое бесхозное состояние UX, когда пользователь вводит /stop. Для LINE это означает изменение записи кэша обратной передачи с «ОЖИДАНИЕ» на «ОШИБКА», чтобы постоянная кнопка «Получить ответ» доставляла сообщение «Выполнение было прервано» вместо зацикливания.
Шаблон: подкласс send для маршрутизации через кэш вместо немедленной отправки
Если ваш UX с медленным откликом кэширует ответ для последующего извлечения (поток обратной передачи LINE), ваше переопределение send должно распознавать три режима:
Ожидание обратной передачи активно для этого чата → кэшируйте ответ под request_id, не отправляйте ничего видимого.
Подтверждение занятости системы (⚡ Прерывание, ⏳ В очереди, ⏩ Управляемое) → обходит кеш и отправляет сообщение видимым образом, чтобы пользователь видел ответ шлюза на его ввод.
Обычный ответ → отправьте сообщение с помощью маркера ответа или push-уведомления, как обычно.
_SYSTEM_BYPASS_PREFIXES — это собственные префиксы подтверждения занятости шлюза (⚡, ⏳, ⏩, 💾). Всегда пропускайте их видимым образом, независимо от кэшированного состояния UX.
Когда этот шаблон уместен
Используйте подход переопределения цикла ввода, когда:
Исходящий API платформы имеет жесткое ограничение временного окна (одноразовый токен ответа, истекающий прикрепленный сеанс и т. д.) И
Видимый пузырь в середине полета является приемлемым UX на этой платформе.
Используйте более простой путь slow_response_threshold = 0 Always-Push, когда:
На платформе нет значимого различия между бесплатным и платным, ИЛИ
Сообщество пользователей предпочитает режим «загрузка… загрузка… ГОТОВО», а не интерактивный промежуточный пузырь.
LINE поддерживает оба варианта: пороговое значение по умолчанию равно 45 секундам для бесплатной выборки обратной передачи, а LINE_SLOW_RESPONSE_THRESHOLD=0 возвращается к «всегда отправлять резервную копию».
Эталонная реализация
См. plugins/platforms/line/adapter.py для полной реализации обратной передачи LINE — конечный автомат RequestCache (PENDING → READY → DELIVERED, плюс ERROR для /stop), переопределение _keep_typing, которое запускает пузырь кнопок шаблона при пороговом значении, переопределение send, которое маршрутизируется через кеш, и переопределение interrupt_session_activity, которое разрешает потерянные ОЖИДАЮЩИЕ записи.
Справочные реализации (путь к плагину)
Полный рабочий пример см. в plugins/platforms/irc/ в репозитории — полностью асинхронный IRC-адаптер с нулевыми внешними зависимостями. plugins/platform/teams/ охватывает Bot Framework/Adaptive Cards, plugins/platforms/google_chat/ охватывает API-интерфейсы REST на основе OAuth, а plugins/platforms/line/ охватывает API-интерфейсы обмена сообщениями на основе веб-перехватчиков с специфичным для платформы медленным пользовательским интерфейсом LLM.
Пошаговый контрольный список (встроенный путь):::примечание
Этот контрольный список предназначен для добавления платформы непосредственно в основную кодовую базу Hermes — обычно это делается основными участниками официально поддерживаемых платформ. Платформы сообщества и сторонние платформы должны использовать указанный выше Путь к плагину.
1. Перечисление платформ
Добавьте свою платформу в перечисление «Platform» в «gateway/config.py»:
fromgateway.configimportPlatform,PlatformConfigfromgateway.platforms.baseimport(BasePlatformAdapter,MessageEvent,MessageType,SendResult,)defcheck_newplat_requirements()->bool:"""Return True if dependencies are available."""returnSOME_SDK_AVAILABLEclassNewPlatAdapter(BasePlatformAdapter):def__init__(self,config:PlatformConfig):super().__init__(config,Platform.NEWPLAT)# Read config from config.extra dictextra=config.extraor{}self._api_key=extra.get("api_key")oros.getenv("NEWPLAT_API_KEY","")asyncdefconnect(self,*,is_reconnect:bool=False)->bool:# Set up connection, start polling/webhookself._mark_connected()returnTrueasyncdefdisconnect(self)->None:self._running=Falseself._mark_disconnected()asyncdefsend(self,chat_id,content,reply_to=None,metadata=None):# Send message via platform APIreturnSendResult(success=True,message_id="...")asyncdefget_chat_info(self,chat_id):return{"name":chat_id,"type":"dm"}
Для входящих сообщений создайте MessageEvent и вызовите self.handle_message(event):
source=self.build_source(chat_id=chat_id,chat_name=name,chat_type="dm",# or "group"user_id=user_id,user_name=user_name,)event=MessageEvent(text=content,message_type=MessageType.TEXT,source=source,message_id=msg_id,)awaitself.handle_message(event)
3. Конфигурация шлюза (gateway/config.py)
Три точки соприкосновения:
get_connected_platforms() — добавьте проверку необходимых учетных данных вашей платформы.
load_gateway_config() — Добавьте запись карты окружения токена: Platform.NEWPLAT: "NEWPLAT_TOKEN"
_apply_env_overrides() — Сопоставьте все переменные окружения NEWPLAT_* с конфигурацией
_UPDATE_ALLOWED_PLATFORMS замороженный набор — Добавьте Platform.NEWPLAT
5. Кроссплатформенная доставка
gateway/platforms/webhook.py — добавьте "newplat" в кортеж типа доставки.
cron/scheduler.py — добавить в замороженный набор _KNOWN_DELIVERY_PLATFORMS и карту платформы _deliver_result().
6. Интеграция CLI
hermes_cli/config.py — добавьте все переменные NEWPLAT_* в _EXTRA_ENV_KEYS
hermes_cli/gateway.py — добавьте запись в список _PLATFORMS с ключом, меткой, эмодзи, token_var, setup_instructions и переменными.
hermes_cli/platforms.py — добавьте запись PlatformInfo с меткой и default_toolset (используется TUI skills_config иtools_config)
hermes_cli/setup.py — добавьте функцию _setup_newplat() (можно делегировать gateway.py) и добавьте кортеж в список платформ обмена сообщениями.
hermes_cli/status.py — Добавьте запись определения платформы: "NewPlat": ("NEWPLAT_TOKEN", "NEWPLAT_HOME_CHANNEL")
hermes_cli/dump.py — Добавьте "newplat": "NEWPLAT_TOKEN" в словарь обнаружения платформы.
7. Инструменты
tools/send_message_tool.py — Добавьте "newplat": Platform.NEWPLAT на карту платформы.
tools/cronjob_tools.py — добавьте newplat в строку описания цели доставки.
8. Наборы инструментов
toolsets.py — добавьте определение набора инструментов "hermes-newplat" с помощью _HERMES_CORE_TOOLS
toolsets.py — добавьте "hermes-newplat" в список включений "hermes-gateway".
9. Необязательно: подсказки по платформе
agent/prompt_builder.py — Если ваша платформа имеет определенные ограничения на отрисовку (нет уценки, ограничения на длину сообщения и т. д.), добавьте запись в словарь PLATFORM_HINTS. Это вводит рекомендации для конкретной платформы в системную подсказку:
PLATFORM_HINTS={#..."newplat":("You are chatting via NewPlat. It supports markdown formatting ""but has a 4000-character message limit."),}
Не всем платформам нужны подсказки — добавляйте их только в том случае, если поведение агента должно отличаться.
Прежде чем отметить PR новой платформы как завершенный, запустите аудит четности для установленной платформы:
# Find every.py file mentioning the reference platform
search_files"bluebubbles"output_mode="files_only"file_glob="*.py"# Find every.py file mentioning the new platform
search_files"newplat"output_mode="files_only"file_glob="*.py"# Any file in the first set but not the second is a potential gap
Повторите эти действия для файлов «.md» и «.ts». Исследуйте каждый пробел — это перечисление платформ (требует обновления) или ссылка на конкретную платформу (пропустить)?
Общие шаблоны
Адаптеры длинного опроса
Если ваш адаптер использует длинный опрос (например, Telegram или Weixin), используйте задачу цикла опроса:
Для платформ с жесткими сроками ответа (например, 5-секундный лимит WeCom) всегда подтверждайте запрос немедленно и доставляйте ответ агента заранее через API позже. Сеансы агентов длятся 3–30 минут — встроенные ответы в окне обратного вызова невозможны.
Блокировки токенов
Если адаптер поддерживает постоянное соединение с уникальными учетными данными, добавьте блокировку области действия, чтобы предотвратить использование одних и тех же учетных данных двумя профилями:
fromgateway.statusimportacquire_scoped_lock,release_scoped_lockasyncdefconnect(self,*,is_reconnect:bool=False):acquired,_existing=acquire_scoped_lock("newplat",self._token)ifnotacquired:logger.error("Token already in use by another profile")returnFalse#... connectasyncdefdisconnect(self):release_scoped_lock("newplat",self._token)
Эталонные реализации
Адаптер
Узор
Сложность
Хорошая ссылка для
bluebubbles.py
ОТДЫХ + вебхук
Средний
Простая интеграция REST API
weixin.py
Длинный опрос + CDN
Высокий
Обработка мультимедиа, шифрование
plugins/platforms/wecom/callback_adapter.py
Обратный вызов/вебхук
Средний
HTTP-сервер, шифрование AES, несколько приложений
плагины/платформы/irc/adapter.py
Длинный опрос + протокол IRC
Высокий
Полнофункциональный плагин-адаптер с блокировкой токена