mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
docs: add platform adapter developer guide + WeCom Callback docs (#7969)
Add the missing 'Adding a Platform Adapter' developer guide — a comprehensive step-by-step checklist covering all 20+ integration points (enum, adapter, config, runner, CLI, tools, toolsets, cron, webhooks, tests, and docs). Includes common patterns for long-poll, callback/webhook, and token-lock adapters with reference implementations. Also adds full docs coverage for the WeCom Callback platform: - New docs page: user-guide/messaging/wecom-callback.md - Environment variables reference (9 WECOM_CALLBACK_* vars) - Toolsets reference (hermes-wecom-callback) - Messaging index (comparison table, architecture diagram, toolsets, security, next-steps links) - Integrations index listing - Sidebar entries for both new pages
This commit is contained in:
parent
fc417ed049
commit
289d2745af
10 changed files with 431 additions and 1 deletions
|
|
@ -33,6 +33,7 @@ PLATFORMS: OrderedDict[str, PlatformInfo] = OrderedDict([
|
|||
("dingtalk", PlatformInfo(label="💬 DingTalk", default_toolset="hermes-dingtalk")),
|
||||
("feishu", PlatformInfo(label="🪽 Feishu", default_toolset="hermes-feishu")),
|
||||
("wecom", PlatformInfo(label="💬 WeCom", default_toolset="hermes-wecom")),
|
||||
("wecom_callback", PlatformInfo(label="💬 WeCom Callback", default_toolset="hermes-wecom-callback")),
|
||||
("weixin", PlatformInfo(label="💬 Weixin", default_toolset="hermes-weixin")),
|
||||
("webhook", PlatformInfo(label="🔗 Webhook", default_toolset="hermes-webhook")),
|
||||
("api_server", PlatformInfo(label="🌐 API Server", default_toolset="hermes-api-server")),
|
||||
|
|
|
|||
|
|
@ -2005,6 +2005,12 @@ def _setup_wecom():
|
|||
_gateway_setup_wecom()
|
||||
|
||||
|
||||
def _setup_wecom_callback():
|
||||
"""Configure WeCom Callback (self-built app) via gateway setup."""
|
||||
from hermes_cli.gateway import _setup_wecom_callback as _gw_setup
|
||||
_gw_setup()
|
||||
|
||||
|
||||
def _setup_bluebubbles():
|
||||
"""Configure BlueBubbles iMessage gateway."""
|
||||
print_header("BlueBubbles (iMessage)")
|
||||
|
|
@ -2130,6 +2136,7 @@ _GATEWAY_PLATFORMS = [
|
|||
("DingTalk", "DINGTALK_CLIENT_ID", _setup_dingtalk),
|
||||
("Feishu / Lark", "FEISHU_APP_ID", _setup_feishu),
|
||||
("WeCom (Enterprise WeChat)", "WECOM_BOT_ID", _setup_wecom),
|
||||
("WeCom Callback (Self-Built App)", "WECOM_CALLBACK_CORP_ID", _setup_wecom_callback),
|
||||
("Weixin (WeChat)", "WEIXIN_ACCOUNT_ID", _setup_weixin),
|
||||
("BlueBubbles (iMessage)", "BLUEBUBBLES_SERVER_URL", _setup_bluebubbles),
|
||||
("Webhooks (GitHub, GitLab, etc.)", "WEBHOOK_ENABLED", _setup_webhooks),
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ def show_status(args):
|
|||
"DingTalk": ("DINGTALK_CLIENT_ID", None),
|
||||
"Feishu": ("FEISHU_APP_ID", "FEISHU_HOME_CHANNEL"),
|
||||
"WeCom": ("WECOM_BOT_ID", "WECOM_HOME_CHANNEL"),
|
||||
"WeCom Callback": ("WECOM_CALLBACK_CORP_ID", None),
|
||||
"Weixin": ("WEIXIN_ACCOUNT_ID", "WEIXIN_HOME_CHANNEL"),
|
||||
"BlueBubbles": ("BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_HOME_CHANNEL"),
|
||||
}
|
||||
|
|
|
|||
256
website/docs/developer-guide/adding-platform-adapters.md
Normal file
256
website/docs/developer-guide/adding-platform-adapters.md
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
---
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Adding a Platform Adapter
|
||||
|
||||
This guide covers adding a new messaging platform to the Hermes gateway. A platform adapter connects Hermes to an external messaging service (Telegram, Discord, WeCom, etc.) so users can interact with the agent through that service.
|
||||
|
||||
:::tip
|
||||
Adding a platform adapter touches 20+ files across code, config, and docs. Use this guide as a checklist — the adapter file itself is typically only 40% of the work.
|
||||
:::
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
User ↔ Messaging Platform ↔ Platform Adapter ↔ Gateway Runner ↔ AIAgent
|
||||
```
|
||||
|
||||
Every adapter extends `BasePlatformAdapter` from `gateway/platforms/base.py` and implements:
|
||||
|
||||
- **`connect()`** — Establish connection (WebSocket, long-poll, HTTP server, etc.)
|
||||
- **`disconnect()`** — Clean shutdown
|
||||
- **`send()`** — Send a text message to a chat
|
||||
- **`send_typing()`** — Show typing indicator (optional)
|
||||
- **`get_chat_info()`** — Return chat metadata
|
||||
|
||||
Inbound messages are received by the adapter and forwarded via `self.handle_message(event)`, which the base class routes to the gateway runner.
|
||||
|
||||
## Step-by-Step Checklist
|
||||
|
||||
### 1. Platform Enum
|
||||
|
||||
Add your platform to the `Platform` enum in `gateway/config.py`:
|
||||
|
||||
```python
|
||||
class Platform(str, Enum):
|
||||
# ... existing platforms ...
|
||||
NEWPLAT = "newplat"
|
||||
```
|
||||
|
||||
### 2. Adapter File
|
||||
|
||||
Create `gateway/platforms/newplat.py`:
|
||||
|
||||
```python
|
||||
from gateway.config import Platform, PlatformConfig
|
||||
from gateway.platforms.base import (
|
||||
BasePlatformAdapter, MessageEvent, MessageType, SendResult,
|
||||
)
|
||||
|
||||
def check_newplat_requirements() -> bool:
|
||||
"""Return True if dependencies are available."""
|
||||
return SOME_SDK_AVAILABLE
|
||||
|
||||
class NewPlatAdapter(BasePlatformAdapter):
|
||||
def __init__(self, config: PlatformConfig):
|
||||
super().__init__(config, Platform.NEWPLAT)
|
||||
# Read config from config.extra dict
|
||||
extra = config.extra or {}
|
||||
self._api_key = extra.get("api_key") or os.getenv("NEWPLAT_API_KEY", "")
|
||||
|
||||
async def connect(self) -> bool:
|
||||
# Set up connection, start polling/webhook
|
||||
self._mark_connected()
|
||||
return True
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
self._running = False
|
||||
self._mark_disconnected()
|
||||
|
||||
async def send(self, chat_id, content, reply_to=None, metadata=None):
|
||||
# Send message via platform API
|
||||
return SendResult(success=True, message_id="...")
|
||||
|
||||
async def get_chat_info(self, chat_id):
|
||||
return {"name": chat_id, "type": "dm"}
|
||||
```
|
||||
|
||||
For inbound messages, build a `MessageEvent` and call `self.handle_message(event)`:
|
||||
|
||||
```python
|
||||
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,
|
||||
)
|
||||
await self.handle_message(event)
|
||||
```
|
||||
|
||||
### 3. Gateway Config (`gateway/config.py`)
|
||||
|
||||
Three touchpoints:
|
||||
|
||||
1. **`get_connected_platforms()`** — Add a check for your platform's required credentials
|
||||
2. **`load_gateway_config()`** — Add token env map entry: `Platform.NEWPLAT: "NEWPLAT_TOKEN"`
|
||||
3. **`_apply_env_overrides()`** — Map all `NEWPLAT_*` env vars to config
|
||||
|
||||
### 4. Gateway Runner (`gateway/run.py`)
|
||||
|
||||
Five touchpoints:
|
||||
|
||||
1. **`_create_adapter()`** — Add an `elif platform == Platform.NEWPLAT:` branch
|
||||
2. **`_is_user_authorized()` allowed_users map** — `Platform.NEWPLAT: "NEWPLAT_ALLOWED_USERS"`
|
||||
3. **`_is_user_authorized()` allow_all map** — `Platform.NEWPLAT: "NEWPLAT_ALLOW_ALL_USERS"`
|
||||
4. **Early env check `_any_allowlist` tuple** — Add `"NEWPLAT_ALLOWED_USERS"`
|
||||
5. **Early env check `_allow_all` tuple** — Add `"NEWPLAT_ALLOW_ALL_USERS"`
|
||||
6. **`_UPDATE_ALLOWED_PLATFORMS` frozenset** — Add `Platform.NEWPLAT`
|
||||
|
||||
### 5. Cross-Platform Delivery
|
||||
|
||||
1. **`gateway/platforms/webhook.py`** — Add `"newplat"` to the delivery type tuple
|
||||
2. **`cron/scheduler.py`** — Add to `_KNOWN_DELIVERY_PLATFORMS` frozenset and `_deliver_result()` platform map
|
||||
|
||||
### 6. CLI Integration
|
||||
|
||||
1. **`hermes_cli/config.py`** — Add all `NEWPLAT_*` vars to `_EXTRA_ENV_KEYS`
|
||||
2. **`hermes_cli/gateway.py`** — Add entry to `_PLATFORMS` list with key, label, emoji, token_var, setup_instructions, and vars
|
||||
3. **`hermes_cli/platforms.py`** — Add `PlatformInfo` entry with label and default_toolset (used by `skills_config` and `tools_config` TUIs)
|
||||
4. **`hermes_cli/setup.py`** — Add `_setup_newplat()` function (can delegate to `gateway.py`) and add tuple to the messaging platforms list
|
||||
5. **`hermes_cli/status.py`** — Add platform detection entry: `"NewPlat": ("NEWPLAT_TOKEN", "NEWPLAT_HOME_CHANNEL")`
|
||||
6. **`hermes_cli/dump.py`** — Add `"newplat": "NEWPLAT_TOKEN"` to platform detection dict
|
||||
|
||||
### 7. Tools
|
||||
|
||||
1. **`tools/send_message_tool.py`** — Add `"newplat": Platform.NEWPLAT` to platform map
|
||||
2. **`tools/cronjob_tools.py`** — Add `newplat` to the delivery target description string
|
||||
|
||||
### 8. Toolsets
|
||||
|
||||
1. **`toolsets.py`** — Add `"hermes-newplat"` toolset definition with `_HERMES_CORE_TOOLS`
|
||||
2. **`toolsets.py`** — Add `"hermes-newplat"` to the `"hermes-gateway"` includes list
|
||||
|
||||
### 9. Optional: Platform Hints
|
||||
|
||||
**`agent/prompt_builder.py`** — If your platform has specific rendering limitations (no markdown, message length limits, etc.), add an entry to the `_PLATFORM_HINTS` dict. This injects platform-specific guidance into the system prompt:
|
||||
|
||||
```python
|
||||
_PLATFORM_HINTS = {
|
||||
# ...
|
||||
"newplat": (
|
||||
"You are chatting via NewPlat. It supports markdown formatting "
|
||||
"but has a 4000-character message limit."
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
Not all platforms need hints — only add one if the agent's behavior should differ.
|
||||
|
||||
### 10. Tests
|
||||
|
||||
Create `tests/gateway/test_newplat.py` covering:
|
||||
|
||||
- Adapter construction from config
|
||||
- Message event building
|
||||
- Send method (mock the external API)
|
||||
- Platform-specific features (encryption, routing, etc.)
|
||||
|
||||
### 11. Documentation
|
||||
|
||||
| File | What to add |
|
||||
|------|-------------|
|
||||
| `website/docs/user-guide/messaging/newplat.md` | Full platform setup page |
|
||||
| `website/docs/user-guide/messaging/index.md` | Platform comparison table, architecture diagram, toolsets table, security section, next-steps link |
|
||||
| `website/docs/reference/environment-variables.md` | All NEWPLAT_* env vars |
|
||||
| `website/docs/reference/toolsets-reference.md` | hermes-newplat toolset |
|
||||
| `website/docs/integrations/index.md` | Platform link |
|
||||
| `website/sidebars.ts` | Sidebar entry for the docs page |
|
||||
| `website/docs/developer-guide/architecture.md` | Adapter count + listing |
|
||||
| `website/docs/developer-guide/gateway-internals.md` | Adapter file listing |
|
||||
|
||||
## Parity Audit
|
||||
|
||||
Before marking a new platform PR as complete, run a parity audit against an established platform:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
Repeat for `.md` and `.ts` files. Investigate each gap — is it a platform enumeration (needs updating) or a platform-specific reference (skip)?
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Long-Poll Adapters
|
||||
|
||||
If your adapter uses long-polling (like Telegram or Weixin), use a polling loop task:
|
||||
|
||||
```python
|
||||
async def connect(self):
|
||||
self._poll_task = asyncio.create_task(self._poll_loop())
|
||||
self._mark_connected()
|
||||
|
||||
async def _poll_loop(self):
|
||||
while self._running:
|
||||
messages = await self._fetch_updates()
|
||||
for msg in messages:
|
||||
await self.handle_message(self._build_event(msg))
|
||||
```
|
||||
|
||||
### Callback/Webhook Adapters
|
||||
|
||||
If the platform pushes messages to your endpoint (like WeCom Callback), run an HTTP server:
|
||||
|
||||
```python
|
||||
async def connect(self):
|
||||
self._app = web.Application()
|
||||
self._app.router.add_post("/callback", self._handle_callback)
|
||||
# ... start aiohttp server
|
||||
self._mark_connected()
|
||||
|
||||
async def _handle_callback(self, request):
|
||||
event = self._build_event(await request.text())
|
||||
await self._message_queue.put(event)
|
||||
return web.Response(text="success") # Acknowledge immediately
|
||||
```
|
||||
|
||||
For platforms with tight response deadlines (e.g., WeCom's 5-second limit), always acknowledge immediately and deliver the agent's reply proactively via API later. Agent sessions run 3–30 minutes — inline replies within a callback response window are not feasible.
|
||||
|
||||
### Token Locks
|
||||
|
||||
If the adapter holds a persistent connection with a unique credential, add a scoped lock to prevent two profiles from using the same credential:
|
||||
|
||||
```python
|
||||
from gateway.status import acquire_scoped_lock, release_scoped_lock
|
||||
|
||||
async def connect(self):
|
||||
if not acquire_scoped_lock("newplat", self._token):
|
||||
logger.error("Token already in use by another profile")
|
||||
return False
|
||||
# ... connect
|
||||
|
||||
async def disconnect(self):
|
||||
release_scoped_lock("newplat", self._token)
|
||||
```
|
||||
|
||||
## Reference Implementations
|
||||
|
||||
| Adapter | Pattern | Complexity | Good reference for |
|
||||
|---------|---------|------------|-------------------|
|
||||
| `bluebubbles.py` | REST + webhook | Medium | Simple REST API integration |
|
||||
| `weixin.py` | Long-poll + CDN | High | Media handling, encryption |
|
||||
| `wecom_callback.py` | Callback/webhook | Medium | HTTP server, AES crypto, multi-app |
|
||||
| `telegram.py` | Long-poll + Bot API | High | Full-featured adapter with groups, threads |
|
||||
|
|
@ -82,7 +82,7 @@ Speech-to-text supports three providers: local Whisper (free, runs on-device), G
|
|||
|
||||
Hermes runs as a gateway bot on 15+ messaging platforms, all configured through the same `gateway` subsystem:
|
||||
|
||||
- **[Telegram](/docs/user-guide/messaging/telegram)**, **[Discord](/docs/user-guide/messaging/discord)**, **[Slack](/docs/user-guide/messaging/slack)**, **[WhatsApp](/docs/user-guide/messaging/whatsapp)**, **[Signal](/docs/user-guide/messaging/signal)**, **[Matrix](/docs/user-guide/messaging/matrix)**, **[Mattermost](/docs/user-guide/messaging/mattermost)**, **[Email](/docs/user-guide/messaging/email)**, **[SMS](/docs/user-guide/messaging/sms)**, **[DingTalk](/docs/user-guide/messaging/dingtalk)**, **[Feishu/Lark](/docs/user-guide/messaging/feishu)**, **[WeCom](/docs/user-guide/messaging/wecom)**, **[Weixin](/docs/user-guide/messaging/weixin)**, **[BlueBubbles](/docs/user-guide/messaging/bluebubbles)**, **[Home Assistant](/docs/user-guide/messaging/homeassistant)**, **[Webhooks](/docs/user-guide/messaging/webhooks)**
|
||||
- **[Telegram](/docs/user-guide/messaging/telegram)**, **[Discord](/docs/user-guide/messaging/discord)**, **[Slack](/docs/user-guide/messaging/slack)**, **[WhatsApp](/docs/user-guide/messaging/whatsapp)**, **[Signal](/docs/user-guide/messaging/signal)**, **[Matrix](/docs/user-guide/messaging/matrix)**, **[Mattermost](/docs/user-guide/messaging/mattermost)**, **[Email](/docs/user-guide/messaging/email)**, **[SMS](/docs/user-guide/messaging/sms)**, **[DingTalk](/docs/user-guide/messaging/dingtalk)**, **[Feishu/Lark](/docs/user-guide/messaging/feishu)**, **[WeCom](/docs/user-guide/messaging/wecom)**, **[WeCom Callback](/docs/user-guide/messaging/wecom-callback)**, **[Weixin](/docs/user-guide/messaging/weixin)**, **[BlueBubbles](/docs/user-guide/messaging/bluebubbles)**, **[Home Assistant](/docs/user-guide/messaging/homeassistant)**, **[Webhooks](/docs/user-guide/messaging/webhooks)**
|
||||
|
||||
See the [Messaging Gateway overview](/docs/user-guide/messaging) for the platform comparison table and setup guide.
|
||||
|
||||
|
|
|
|||
|
|
@ -232,6 +232,15 @@ For cloud sandbox backends, persistence is filesystem-oriented. `TERMINAL_LIFETI
|
|||
| `WECOM_WEBSOCKET_URL` | Custom WebSocket URL (default: `wss://openws.work.weixin.qq.com`) |
|
||||
| `WECOM_ALLOWED_USERS` | Comma-separated WeCom user IDs allowed to message the bot |
|
||||
| `WECOM_HOME_CHANNEL` | WeCom chat ID for cron delivery and notifications |
|
||||
| `WECOM_CALLBACK_CORP_ID` | WeCom enterprise Corp ID for callback self-built app |
|
||||
| `WECOM_CALLBACK_CORP_SECRET` | Corp secret for the self-built app |
|
||||
| `WECOM_CALLBACK_AGENT_ID` | Agent ID of the self-built app |
|
||||
| `WECOM_CALLBACK_TOKEN` | Callback verification token |
|
||||
| `WECOM_CALLBACK_ENCODING_AES_KEY` | AES key for callback encryption |
|
||||
| `WECOM_CALLBACK_HOST` | Callback server bind address (default: `0.0.0.0`) |
|
||||
| `WECOM_CALLBACK_PORT` | Callback server port (default: `8645`) |
|
||||
| `WECOM_CALLBACK_ALLOWED_USERS` | Comma-separated user IDs for allowlist |
|
||||
| `WECOM_CALLBACK_ALLOW_ALL_USERS` | Set `true` to allow all users without an allowlist |
|
||||
| `WEIXIN_ACCOUNT_ID` | Weixin account ID obtained via QR login through iLink Bot API |
|
||||
| `WEIXIN_TOKEN` | Weixin authentication token obtained via QR login through iLink Bot API |
|
||||
| `WEIXIN_BASE_URL` | Override Weixin iLink Bot API base URL (default: `https://ilinkai.weixin.qq.com`) |
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ Platform toolsets define the complete tool configuration for a deployment target
|
|||
| `hermes-dingtalk` | Same as `hermes-cli`. |
|
||||
| `hermes-feishu` | Same as `hermes-cli`. |
|
||||
| `hermes-wecom` | Same as `hermes-cli`. |
|
||||
| `hermes-wecom-callback` | WeCom callback toolset — enterprise self-built app messaging (full access). |
|
||||
| `hermes-weixin` | Same as `hermes-cli`. |
|
||||
| `hermes-bluebubbles` | Same as `hermes-cli`. |
|
||||
| `hermes-homeassistant` | Same as `hermes-cli`. |
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ For the full voice feature set — including CLI microphone mode, spoken replies
|
|||
| DingTalk | — | — | — | — | — | ✅ | ✅ |
|
||||
| Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| WeCom | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
|
||||
| WeCom Callback | — | — | — | — | — | — | — |
|
||||
| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
|
||||
| BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — |
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ flowchart TB
|
|||
dt[DingTalk]
|
||||
fs[Feishu/Lark]
|
||||
wc[WeCom]
|
||||
wcb[WeCom Callback]
|
||||
wx[Weixin]
|
||||
bb[BlueBubbles]
|
||||
api["API Server<br/>(OpenAI-compatible)"]
|
||||
|
|
@ -75,6 +77,7 @@ flowchart TB
|
|||
dt --> store
|
||||
fs --> store
|
||||
wc --> store
|
||||
wcb --> store
|
||||
wx --> store
|
||||
bb --> store
|
||||
api --> store
|
||||
|
|
@ -180,6 +183,7 @@ MATRIX_ALLOWED_USERS=@alice:matrix.org
|
|||
DINGTALK_ALLOWED_USERS=user-id-1
|
||||
FEISHU_ALLOWED_USERS=ou_xxxxxxxx,ou_yyyyyyyy
|
||||
WECOM_ALLOWED_USERS=user-id-1,user-id-2
|
||||
WECOM_CALLBACK_ALLOWED_USERS=user-id-1,user-id-2
|
||||
|
||||
# Or allow
|
||||
GATEWAY_ALLOWED_USERS=123456789,987654321
|
||||
|
|
@ -362,6 +366,7 @@ Each platform has its own toolset:
|
|||
| DingTalk | `hermes-dingtalk` | Full tools including terminal |
|
||||
| Feishu/Lark | `hermes-feishu` | Full tools including terminal |
|
||||
| WeCom | `hermes-wecom` | Full tools including terminal |
|
||||
| WeCom Callback | `hermes-wecom-callback` | Full tools including terminal |
|
||||
| Weixin | `hermes-weixin` | Full tools including terminal |
|
||||
| BlueBubbles | `hermes-bluebubbles` | Full tools including terminal |
|
||||
| API Server | `hermes` (default) | Full tools including terminal |
|
||||
|
|
@ -382,6 +387,7 @@ Each platform has its own toolset:
|
|||
- [DingTalk Setup](dingtalk.md)
|
||||
- [Feishu/Lark Setup](feishu.md)
|
||||
- [WeCom Setup](wecom.md)
|
||||
- [WeCom Callback Setup](wecom-callback.md)
|
||||
- [Weixin Setup (WeChat)](weixin.md)
|
||||
- [BlueBubbles Setup (iMessage)](bluebubbles.md)
|
||||
- [Open WebUI + API Server](open-webui.md)
|
||||
|
|
|
|||
147
website/docs/user-guide/messaging/wecom-callback.md
Normal file
147
website/docs/user-guide/messaging/wecom-callback.md
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
sidebar_position: 15
|
||||
---
|
||||
|
||||
# WeCom Callback (Self-Built App)
|
||||
|
||||
Connect Hermes to WeCom (Enterprise WeChat) as a self-built enterprise application using the callback/webhook model.
|
||||
|
||||
:::info WeCom Bot vs WeCom Callback
|
||||
Hermes supports two WeCom integration modes:
|
||||
- **[WeCom Bot](wecom.md)** — bot-style, connects via WebSocket. Simpler setup, works in group chats.
|
||||
- **WeCom Callback** (this page) — self-built app, receives encrypted XML callbacks. Shows as a first-class app in users' WeCom sidebar. Supports multi-corp routing.
|
||||
:::
|
||||
|
||||
## How It Works
|
||||
|
||||
1. You register a self-built application in the WeCom Admin Console
|
||||
2. WeCom pushes encrypted XML to your HTTP callback endpoint
|
||||
3. Hermes decrypts the message, queues it for the agent
|
||||
4. Immediately acknowledges (silent — nothing displayed to the user)
|
||||
5. The agent processes the request (typically 3–30 minutes)
|
||||
6. The reply is delivered proactively via the WeCom `message/send` API
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A WeCom enterprise account with admin access
|
||||
- `aiohttp` and `httpx` Python packages (included in the default install)
|
||||
- A publicly reachable server for the callback URL (or a tunnel like ngrok)
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create a Self-Built App in WeCom
|
||||
|
||||
1. Go to [WeCom Admin Console](https://work.weixin.qq.com/) → **Applications** → **Create App**
|
||||
2. Note your **Corp ID** (shown at the top of the admin console)
|
||||
3. In the app settings, create a **Corp Secret**
|
||||
4. Note the **Agent ID** from the app's overview page
|
||||
5. Under **Receive Messages**, configure the callback URL:
|
||||
- URL: `http://YOUR_PUBLIC_IP:8645/wecom/callback`
|
||||
- Token: Generate a random token (WeCom provides one)
|
||||
- EncodingAESKey: Generate a key (WeCom provides one)
|
||||
|
||||
### 2. Configure Environment Variables
|
||||
|
||||
Add to your `.env` file:
|
||||
|
||||
```bash
|
||||
WECOM_CALLBACK_CORP_ID=your-corp-id
|
||||
WECOM_CALLBACK_CORP_SECRET=your-corp-secret
|
||||
WECOM_CALLBACK_AGENT_ID=1000002
|
||||
WECOM_CALLBACK_TOKEN=your-callback-token
|
||||
WECOM_CALLBACK_ENCODING_AES_KEY=your-43-char-aes-key
|
||||
|
||||
# Optional
|
||||
WECOM_CALLBACK_HOST=0.0.0.0
|
||||
WECOM_CALLBACK_PORT=8645
|
||||
WECOM_CALLBACK_ALLOWED_USERS=user1,user2
|
||||
```
|
||||
|
||||
### 3. Start the Gateway
|
||||
|
||||
```bash
|
||||
hermes gateway start
|
||||
```
|
||||
|
||||
The callback adapter starts an HTTP server on the configured port. WeCom will verify the callback URL via a GET request, then begin sending messages via POST.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
Set these in `config.yaml` under `platforms.wecom_callback.extra`, or use environment variables:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `corp_id` | — | WeCom enterprise Corp ID (required) |
|
||||
| `corp_secret` | — | Corp secret for the self-built app (required) |
|
||||
| `agent_id` | — | Agent ID of the self-built app (required) |
|
||||
| `token` | — | Callback verification token (required) |
|
||||
| `encoding_aes_key` | — | 43-character AES key for callback encryption (required) |
|
||||
| `host` | `0.0.0.0` | Bind address for the HTTP callback server |
|
||||
| `port` | `8645` | Port for the HTTP callback server |
|
||||
| `path` | `/wecom/callback` | URL path for the callback endpoint |
|
||||
|
||||
## Multi-App Routing
|
||||
|
||||
For enterprises running multiple self-built apps (e.g., across different departments or subsidiaries), configure the `apps` list in `config.yaml`:
|
||||
|
||||
```yaml
|
||||
platforms:
|
||||
wecom_callback:
|
||||
enabled: true
|
||||
extra:
|
||||
host: "0.0.0.0"
|
||||
port: 8645
|
||||
apps:
|
||||
- name: "dept-a"
|
||||
corp_id: "ww_corp_a"
|
||||
corp_secret: "secret-a"
|
||||
agent_id: "1000002"
|
||||
token: "token-a"
|
||||
encoding_aes_key: "key-a-43-chars..."
|
||||
- name: "dept-b"
|
||||
corp_id: "ww_corp_b"
|
||||
corp_secret: "secret-b"
|
||||
agent_id: "1000003"
|
||||
token: "token-b"
|
||||
encoding_aes_key: "key-b-43-chars..."
|
||||
```
|
||||
|
||||
Users are scoped by `corp_id:user_id` to prevent cross-corp collisions. When a user sends a message, the adapter records which app (corp) they belong to and routes replies through the correct app's access token.
|
||||
|
||||
## Access Control
|
||||
|
||||
Restrict which users can interact with the app:
|
||||
|
||||
```bash
|
||||
# Allowlist specific users
|
||||
WECOM_CALLBACK_ALLOWED_USERS=zhangsan,lisi,wangwu
|
||||
|
||||
# Or allow all users
|
||||
WECOM_CALLBACK_ALLOW_ALL_USERS=true
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
The adapter exposes:
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/wecom/callback` | URL verification handshake (WeCom sends this during setup) |
|
||||
| POST | `/wecom/callback` | Encrypted message callback (WeCom sends user messages here) |
|
||||
| GET | `/health` | Health check — returns `{"status": "ok"}` |
|
||||
|
||||
## Encryption
|
||||
|
||||
All callback payloads are encrypted with AES-CBC using the EncodingAESKey. The adapter handles:
|
||||
|
||||
- **Inbound**: Decrypt XML payload, verify SHA1 signature
|
||||
- **Outbound**: Replies sent via proactive API (not encrypted callback response)
|
||||
|
||||
The crypto implementation is compatible with Tencent's official WXBizMsgCrypt SDK.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **No streaming** — replies arrive as complete messages after the agent finishes
|
||||
- **No typing indicators** — the callback model doesn't support typing status
|
||||
- **Text only** — currently supports text messages; image/file/voice not yet implemented
|
||||
- **Response latency** — agent sessions take 3–30 minutes; users see the reply when processing completes
|
||||
|
|
@ -108,6 +108,7 @@ const sidebars: SidebarsConfig = {
|
|||
'user-guide/messaging/dingtalk',
|
||||
'user-guide/messaging/feishu',
|
||||
'user-guide/messaging/wecom',
|
||||
'user-guide/messaging/wecom-callback',
|
||||
'user-guide/messaging/weixin',
|
||||
'user-guide/messaging/bluebubbles',
|
||||
'user-guide/messaging/open-webui',
|
||||
|
|
@ -175,6 +176,7 @@ const sidebars: SidebarsConfig = {
|
|||
items: [
|
||||
'developer-guide/adding-tools',
|
||||
'developer-guide/adding-providers',
|
||||
'developer-guide/adding-platform-adapters',
|
||||
'developer-guide/memory-provider-plugin',
|
||||
'developer-guide/context-engine-plugin',
|
||||
'developer-guide/creating-skills',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue