WeChat AI Agent SDK with ACP (Agent Client Protocol) support
Project description
wechat-agent-sdk
English | 中文
WeChat AI Agent bridge framework — connect any AI backend to WeChat with a simple Agent interface, or embed as a transport layer in your platform.
Supports ACP (Agent Client Protocol) to bridge Claude Code, Codex, Kimi, and other ACP-compatible agents directly to WeChat.
This is not an official WeChat project. Built on the iLink Bot API, for learning and research purposes only.
Architecture
The SDK has a two-layer design:
┌─────────────────────────────────────────────────┐
│ Bot Layer (full-stack mode) │
│ WeChatBot / Builder / Manager / Middleware │
│ Agent.chat() dispatching │
│ For: standalone developers │
└──────────────────────┬──────────────────────────┘
│ uses internally
┌──────────────────────▼──────────────────────────┐
│ Transport Layer (headless mode) │
│ WeChatTransport │
│ connect / messages / parse / send / login │
│ For: platform integration │
└──────────────────────┬──────────────────────────┘
│ uses internally
┌──────────────────────▼──────────────────────────┐
│ Infrastructure │
│ ILinkBotClient (7 API endpoints) │
│ MediaPipeline (AES + CDN upload/download) │
│ AccountStorage (JSON / Redis / SQLite) │
└─────────────────────────────────────────────────┘
Standalone developers use the Bot layer — implement Agent.chat(), call bot.run(), done.
Platform integrators use the Transport layer — consume transport.messages(), handle routing/agents yourself, call transport.send_text() to reply.
Installation
pip install wechat-agent-sdk
Optional dependencies:
pip install 'wechat-agent-sdk[qr]' # Terminal QR code display
pip install 'wechat-agent-sdk[acp]' # ACP protocol (Claude Code / Codex / Kimi)
pip install 'wechat-agent-sdk[openai]' # OpenAI integration
pip install 'wechat-agent-sdk[redis]' # Redis storage backend
pip install 'wechat-agent-sdk[sqlite]' # SQLite storage backend
pip install 'wechat-agent-sdk[all]' # Everything
Requires Python >= 3.11
Quick Start (Bot Layer)
For standalone developers who want a bot running in 5 minutes:
import asyncio
from wechat_agent_sdk import Agent, ChatRequest, ChatResponse, WeChatBot
class EchoAgent(Agent):
async def chat(self, request: ChatRequest) -> ChatResponse:
return ChatResponse(text=f"You said: {request.text}")
async def main():
bot = WeChatBot(agent=EchoAgent())
await bot.run()
asyncio.run(main())
Using the Builder pattern:
bot = (
WeChatBot.builder()
.agent(EchoAgent())
.account_id("my_bot")
.storage(RedisStorage("redis://localhost"))
.middleware(rate_limit_middleware)
.on_error(my_error_handler)
.build()
)
await bot.run()
Transport Layer (Platform Integration)
For platforms that have their own agent execution layer, pipeline, and session management. The Transport layer provides only: connect, receive, parse, send.
import asyncio
from wechat_agent_sdk import WeChatTransport, ParsedMessage
async def main():
transport = WeChatTransport(
account_id="bot_1",
storage=my_redis_storage,
)
# Web login (for platforms with web UI)
session = await transport.request_login()
# ... show session.qr_url in your web UI ...
result = await transport.check_login(session)
# Or terminal login
# await transport.login_terminal()
# Receive messages
await transport.connect()
async for raw_msg in transport.messages():
parsed = transport.parse(raw_msg)
if not parsed:
continue
# Your platform handles everything from here:
# permission checks, session management, agent execution, SSE streaming...
reply = await your_pipeline.handle(parsed)
# Send reply via transport
await transport.send_text(parsed.conversation_id, reply, parsed.context_token)
await transport.disconnect()
asyncio.run(main())
Transport API
| Method | Description |
|---|---|
connect() / disconnect() |
Manage connection lifecycle |
messages() |
Async iterator — long-poll for messages (auto dedup + cursor) |
parse(raw) |
Parse raw iLink message → ParsedMessage |
send_text(chat_id, text) |
Send text (auto markdown strip + split) |
send_text_raw(chat_id, text) |
Send raw text (no processing) |
send_media(chat_id, data, type) |
Encrypt + upload + send media |
download_media(media) |
Download + decrypt media attachment |
send_typing(chat_id, start) |
Typing indicator |
request_login() |
Get QR URL for web login |
check_login(session) |
Poll login status |
login_terminal() |
Interactive terminal QR login |
logout() |
Clear token, force re-login |
activate_token(token) |
Inject token (after platform re-login) |
needs_login |
Property: True when no valid token |
Integration Patterns
Pattern A: Claude Code via ACP
npm install -g @zed-industries/claude-code-acp
pip install 'wechat-agent-sdk[acp,qr]'
from wechat_agent_sdk import WeChatBot
from wechat_agent_sdk.acp.adapter import AcpAgent
agent = AcpAgent(command="claude-agent-acp", permission_mode="bypassPermissions")
bot = WeChatBot(agent=agent)
await bot.run()
Supported ACP agents:
| Agent | Install | Command |
|---|---|---|
| Claude Code | npm i -g @zed-industries/claude-code-acp |
claude-agent-acp |
| Codex | npm i -g @openai/codex-acp |
codex-acp |
| Kimi CLI | npm i -g kimi-cli |
kimi (args: ["acp"]) |
Pattern B: OpenAI / Compatible APIs
class OpenAIAgent(Agent):
async def on_start(self):
from openai import AsyncOpenAI
self._client = AsyncOpenAI()
async def chat(self, request: ChatRequest) -> ChatResponse:
resp = await self._client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": request.text}],
)
return ChatResponse(text=resp.choices[0].message.content)
Pattern C: Multi-Account
from wechat_agent_sdk import WeChatBotManager
manager = WeChatBotManager(storage=my_storage, auto_restart=True)
manager.add_bot("bot_1", agent=AgentA())
manager.add_bot("bot_2", agent=AgentB())
await manager.start_all()
# manager.get_status() → {"bot_1": RUNNING, "bot_2": RUNNING}
await manager.stop_all()
Middleware
Bot layer supports an onion middleware chain (inspired by aiogram / Bot Framework):
async def logging_mw(ctx, next_fn):
print(f"Received: {ctx.request.text}")
await next_fn() # call next middleware / handler
print(f"Replied: {ctx.response.text if ctx.response else 'None'}")
async def rate_limit(ctx, next_fn):
if is_rate_limited(ctx.request.conversation_id):
ctx.response = ChatResponse(text="Please slow down")
return # short-circuit, don't call next
await next_fn()
bot = WeChatBot.builder().agent(my_agent).middleware(logging_mw).middleware(rate_limit).build()
Error handling via middleware:
async def my_error_handler(ctx, error):
await alert_ops_team(error)
return ChatResponse(text="Something went wrong, please retry")
bot = WeChatBot.builder().agent(my_agent).on_error(my_error_handler).build()
Concurrent Message Handling
By default, the Bot layer processes up to 10 messages concurrently. When agent.chat() takes a long time (e.g. AcpAgent calling Claude Code), other users' messages are handled in parallel instead of queuing.
# Default: 10 concurrent handlers
bot = WeChatBot(agent=my_agent)
# Custom concurrency limit
bot = WeChatBot(agent=my_agent, max_concurrent=20)
# Via builder
bot = WeChatBot.builder().agent(my_agent).max_concurrent(5).build()
Bounded by asyncio.Semaphore — when the limit is reached, new messages wait until a slot opens. Transport layer does not manage concurrency; platform integrators control their own.
Storage Backends
| Backend | Install | Usage |
|---|---|---|
| JSON file (default) | built-in | JsonFileStorage() |
| Redis | pip install 'wechat-agent-sdk[redis]' |
from wechat_agent_sdk.account.redis_storage import RedisStorage |
| SQLite | pip install 'wechat-agent-sdk[sqlite]' |
from wechat_agent_sdk.account.sqlite_storage import SqliteStorage |
from wechat_agent_sdk.account.redis_storage import RedisStorage
storage = RedisStorage(url="redis://localhost:6379", prefix="wechat-sdk")
bot = WeChatBot(agent=my_agent, storage=storage)
API Reference
ParsedMessage (Transport layer)
| Field | Type | Description |
|---|---|---|
conversation_id |
str |
User wxid (DM) or group ID |
text |
str |
Text content (voice auto-transcribed) |
media |
list[MediaInfo] |
All media attachments (supports multi-image) |
message_id |
str |
Unique message ID |
context_token |
str |
Must echo back when replying |
group_id |
str | None |
Group ID (group chats) |
sender_id |
str | None |
Sender wxid (group chats) |
sender_name |
str | None |
Sender nickname (group chats) |
is_at_bot |
bool |
Whether bot was @mentioned |
raw |
dict | None |
Raw iLink message |
ChatRequest (Bot layer)
Same fields as ParsedMessage except media is MediaInfo | None (first attachment only) and no context_token (handled internally).
ChatResponse
| Field | Type | Description |
|---|---|---|
text |
str | None |
Reply text (markdown auto-stripped) |
media |
MediaResponseInfo | None |
Reply media (image/video/file) |
MediaInfo
| Field | Type | Description |
|---|---|---|
type |
str |
"image" / "audio" / "video" / "file" |
cdn_param |
str |
CDN encrypted query param (for download) |
aes_key |
str |
Base64 AES key |
file_name |
str | None |
Original filename |
Download media: data = await transport.download_media(media_info)
Supported Message Types
Inbound (WeChat → Agent)
| Type | Behavior |
|---|---|
| Text | text contains the message |
| Image | text is "[图片]", media contains CDN reference for download |
| Voice | Transcribed text if available, else "[语音]" |
| Video | text is "[视频]", media contains CDN reference |
| File | text is "[文件: xxx.pdf]", media contains CDN reference |
| Quote | Quoted text prepended: "[引用: original] new message" |
Outbound (Agent → WeChat)
| Type | Usage |
|---|---|
| Text | ChatResponse(text="...") |
| Long text | Auto-split at paragraph boundaries (max 2000 chars) |
| Markdown | Auto-stripped to plain text |
| Image/Video/File | ChatResponse(media=MediaResponseInfo(type="image", url="path_or_url")) |
Project Structure
src/wechat_agent_sdk/
├── __init__.py # Public API exports
├── agent.py # Agent abstract base class
├── types.py # ChatRequest, ChatResponse, MediaInfo
├── transport.py # WeChatTransport + ParsedMessage (transport layer)
├── middleware.py # MiddlewareChain + Context (bot layer)
├── api/
│ ├── client.py # ILinkBotClient (7 API endpoints)
│ ├── auth.py # Login flow + LoginSession/LoginResult
│ └── types.py # iLink API data models
├── messaging/
│ ├── process.py # Inbound message parsing
│ ├── send.py # Text splitting + markdown conversion
│ └── monitor.py # Legacy monitor (kept for backward compat)
├── media/
│ ├── crypto.py # AES-128-ECB + dual-format key decoding
│ └── cdn.py # CDN upload / download
├── acp/
│ └── adapter.py # AcpAgent: ACP subprocess bridge
├── account/
│ ├── manager.py # WeChatBot + WeChatBotBuilder
│ ├── bot_manager.py # WeChatBotManager (multi-account)
│ ├── storage.py # AccountStorage ABC + JsonFileStorage
│ ├── redis_storage.py # RedisStorage (optional)
│ └── sqlite_storage.py # SqliteStorage (optional)
└── utils/
└── markdown.py # strip_markdown()
Acknowledgements
- wong2/weixin-agent-sdk — architecture reference
- m1heng/claude-plugin-weixin — Claude integration reference
- Agent Client Protocol — ACP specification
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wechat_agent_sdk-0.2.1.tar.gz.
File metadata
- Download URL: wechat_agent_sdk-0.2.1.tar.gz
- Upload date:
- Size: 49.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee4fc73d61cb39e29b725dfc49f6f99b4f6a5fecfceb4f07c6fb3228a3529320
|
|
| MD5 |
c1dd2b41202e3163ba7339b26f6773c7
|
|
| BLAKE2b-256 |
bb098d5db94609153355ec869134cb13c5614ca92b7e7672f703d43123939b23
|
File details
Details for the file wechat_agent_sdk-0.2.1-py3-none-any.whl.
File metadata
- Download URL: wechat_agent_sdk-0.2.1-py3-none-any.whl
- Upload date:
- Size: 41.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1dff9ed7c0a519792f33e5dcd5ef5f2b5b03a86770b842651dd7986ddc5b2af1
|
|
| MD5 |
aa18fee844068080228bf943b96bf5d1
|
|
| BLAKE2b-256 |
32ac027b96180354d9f502e07a5641bfda4fb271ef1e38bf555fadca21acc694
|