平台适配器负责:
- 将用户输入转换为
Backend.chat()调用 - 监听 Backend 事件,并转换成平台特定输出
- 维护平台自己的会话标识、连接对象、UI 状态
平台与 Backend 的关系是单向依赖:
Platform ──调方法──▶ Backend
Platform ◀──听事件── Backend
Backend 不知道具体平台存在。
src/platforms/
├── base.ts # PlatformAdapter 抽象基类
extensions/
├── console/ # 控制台 TUI(OpenTUI / React)— 内嵌 extension
├── web/ # Web GUI(HTTP + SSE + Vue)— 内嵌 extension
extensions/
├── lark/ # 飞书平台 extension(内嵌)
├── telegram/ # Telegram 平台 extension(内嵌)
├── discord/ # Discord 平台 extension(可选安装)
├── qq/ # QQ 平台 extension(可选安装)
├── wxwork/ # 企业微信平台 extension(可选安装)
└── weixin/ # 微信平台 extension(可选安装)
CLI headless 模式(
src/cli.ts)不是平台适配器,直接调用 Backend。
abstract class PlatformAdapter {
abstract start(): Promise<void>
abstract stop(): Promise<void>
get name(): string
}基类只约束生命周期,不关心消息格式。
所有平台适配器都遵循同一模式:
class XxxPlatform extends PlatformAdapter {
constructor(private backend: Backend, ...) {}
async start() {
this.backend.on('response', (sid, text) => { /* 输出 */ })
this.backend.on('stream:chunk', (sid, chunk) => { /* 流式输出 */ })
// 某处收到用户输入后:
await this.backend.chat(sessionId, text)
}
}如果平台支持图片和文档输入,则调用会扩展为:
await this.backend.chat(sessionId, text, images, documents)其中:
images: Array<{ mimeType: string; data: string }>
documents: Array<{ fileName: string; mimeType: string; data: string }>基于 OpenTUI / React 的 TUI 界面。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { modeName?, contextWindow?, configDir, getMCPManager, setMCPManager }) |
| sessionId | 启动时生成时间戳 ID,如 20250715_143052_a7x2 |
| 流式支持 | 支持 |
| 工具状态 | 通过 tool:update 事件实时显示 |
| 指令 | /new、/load、/sh <命令>、/undo、/redo、/agent、/exit 等 |
| 图片输入 | 当前未实现终端内图片上传 |
| 撤销/重做 | 调用 Backend undo('last-visible-message') 与 redo() |
Weixin 不再从 src/platforms/registry.ts 内置注册,而是由 extensions/weixin/manifest.json 注册。该平台属于可选 extension,发行包默认不内嵌,使用前请先执行 iris ext install weixin。
实现基于腾讯微信团队官方 ilink 协议。使用 HTTP 长轮询模式,首次启动自动扫码登录。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { botToken?, baseUrl?, showToolStatus? }) |
| sessionId | weixin-{userId} |
| 通信方式 | HTTP Long-polling(getUpdates),非 WebSocket |
| 流式支持 | 协议支持,但微信不支持消息编辑,因此累积后一次性发送 |
| 工具状态 | 通过 tool:update 事件累积,随最终回复一起发送 |
| 并发控制 | 每个用户同一时间只处理一条消息(busy 锁) |
| 消息缓冲 | AI 输出期间用户新消息暂存到缓冲区,完成后自动合并发送 |
| Markdown | 不支持,自动转换为纯文本 |
| 输入状态 | 支持,通过 sendTyping + typing_ticket 显示"正在输入" |
| 图片输入 | 支持(CDN 加密传输,AES-128-ECB) |
| 登录方式 | 扫码登录,Token 自动缓存到 IRIS_DATA_DIR/configs/weixin-auth.json |
| 指令 | 说明 |
|---|---|
/new |
新建对话(清空上下文) |
/stop |
中止当前 AI 回复 |
/flush |
中止当前回复 + 立即将缓冲消息发送给 AI |
/model |
查看或切换模型 |
/help |
显示帮助 |
先安装 extension:iris ext install weixin。然后在 ~/.iris/configs/platform.yaml 中设置 type: weixin(或加入多平台数组)。
首次启动时无需手动配置凭证,Iris 会自动弹出二维码链接,用微信扫码即可完成登录。登录成功后 Token 自动缓存到 IRIS_DATA_DIR/configs/weixin-auth.json,后续启动无需再次扫码。
type: weixin
weixin:
# 可选:手动指定 Bot Token
# botToken: your-bot-token
# 可选:覆盖 API 基地址
# baseUrl: https://ilinkai.weixin.qq.com
# showToolStatus: true| 微信(Weixin) | 企业微信(WXWork) | |
|---|---|---|
| 平台 | 普通微信 | 企业微信 |
| 协议 | ilink HTTP 长轮询 | WebSocket(@wecom/aibot-node-sdk) |
| 登录 | 扫码登录 | Bot ID + Secret |
| Markdown | 不支持 | 部分支持 |
| 媒体传输 | CDN + AES-128-ECB | SDK 封装 |
WXWork 不再从 src/platforms/registry.ts 内置注册,而是由 extensions/wxwork/manifest.json 注册。该平台属于可选 extension,发行包默认不内嵌,使用前请先执行 iris ext install wxwork。
实现基于腾讯官方 @wecom/aibot-node-sdk 的企业微信智能机器人适配器。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { botId, secret, showToolStatus? }) |
| sessionId | 私聊:wxwork-dm-{userId};群聊:wxwork-group-{chatId} |
| 流式支持 | 支持,通过 replyStream 推送,300ms 节流 |
| 工具状态 | 通过 tool:update 事件实时展示工具执行进度 |
| 并发控制 | 每个 chatKey 同一时间只处理一条消息(busy 锁) |
| 消息缓冲 | AI 输出期间用户新消息暂存到缓冲区,完成后自动合并发送 |
| 工具审批 | 自动批准所有工具调用(企微无交互审批 UI) |
| 图片输入 | 支持图片消息解析并传入 Backend |
| 指令 | 说明 |
|---|---|
/stop |
标记 stopped 并立即关闭流式消息,后续事件忽略 |
/flush |
中止当前回复 + 立即将缓冲消息发送给 AI |
/session |
查看/切换历史会话 |
先安装 extension:iris ext install wxwork。然后在 ~/.iris/configs/platform.yaml 中设置 type: wxwork(或加入多平台数组),并填写 wxwork.botId 和 wxwork.secret。
在企业微信管理后台 → 应用管理 → 智能机器人 中创建并获取 Bot ID 和 Secret。
QQ 不再从 src/platforms/registry.ts 内置注册,而是由 extensions/qq/manifest.json 注册。该平台属于可选 extension,发行包默认不内嵌,使用前请先执行 iris ext install qq。
实现基于 OneBot v11 协议,通过 NapCat 框架连接个人 QQ 账号。使用正向 WebSocket 长连接模式。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { wsUrl, accessToken?, selfId, groupMode?, showToolStatus? }) |
| sessionId | 私聊:qq-dm-{userId};群聊:qq-group-{groupId} |
| 流式支持 | 不支持(QQ 不支持消息编辑),兼容流式 Backend 模式(累积后一次性发送) |
| 工具状态 | 通过独立消息通知执行中的工具(可配置关闭) |
| 并发控制 | 每个 chatKey 同一时间只处理一条消息(busy 锁) |
| 消息缓冲 | AI 输出期间用户新消息暂存到缓冲区,完成后自动合并发送 |
| 工具审批 | 自动批准所有工具调用(QQ 无交互审批 UI) |
| 图片输入 | 支持图片消息解析,HTTP 下载后传入 Backend |
| 群聊触发 | 可配置:at(默认,需 @机器人)/ all(响应所有消息)/ off(不响应群聊) |
| 重连机制 | 断线后 5 秒间隔自动重连,最多 100 次 |
| 指令 | 说明 |
|---|---|
/new |
新建对话(清空上下文) |
/clear |
清空当前对话历史 |
/model |
查看或切换模型 |
/session |
查看或切换历史会话 |
/stop |
中止当前 AI 回复 |
/flush |
中止当前回复并立即处理缓冲消息 |
/help |
显示帮助 |
先安装 extension:iris ext install qq。然后在 ~/.iris/configs/platform.yaml 中设置 type: qq(或加入多平台数组),并填写 QQ 相关配置:
type: qq
qq:
wsUrl: "ws://127.0.0.1:3001" # NapCat OneBot v11 正向 WebSocket 地址
selfId: "123456789" # 机器人自身 QQ 号
# accessToken: "your-token" # NapCat 鉴权 token(可选)
# groupMode: at # 群聊触发模式:at / all / off
# showToolStatus: true # 是否展示工具执行状态- 部署 NapCat 并登录 QQ 账号
- 在 NapCat 中创建正向 WebSocket 服务端,消息格式选择 array(数组格式)
- 设置 Token(13 位以上),并将相同的值填入 Iris 配置的
accessToken
Discord 不再从 src/platforms/registry.ts 内置注册,而是由 extensions/discord/manifest.json 注册。该平台属于可选 extension,发行包默认不内嵌,使用前请先执行 iris ext install discord。
实现基于 discord.js 官方 SDK。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { token }) |
| sessionId | discord-{channelId} |
| 流式支持 | 不支持,仅监听 response |
| 消息限制 | 自动分段,每段最多 2000 字符 |
| 图片输入 | 当前未接入 |
先安装 extension:iris ext install discord。然后在 ~/.iris/configs/platform.yaml 中设置 type: discord(或加入多平台数组),并填写 discord.token。
Telegram 不再从 src/platforms/registry.ts 内置注册,而是由 extensions/telegram/manifest.json 在启动时自动注册。运行时入口为 extensions/telegram/dist/index.mjs。
实现基于 grammY 官方 SDK,支持流式输出、多媒体输入、并发控制。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { token, showToolStatus?, groupMentionRequired? }) |
| sessionId | 私聊:telegram-dm-{chatId};群聊:telegram-group-{chatId};话题:telegram-group-{chatId}-thread-{threadId} |
| 流式支持 | 支持,通过 sendMessage + editMessageText 实现实时流式编辑,1500ms 节流 |
| 工具状态 | 通过 tool:update 事件实时展示工具执行进度 |
| 并发控制 | 每个 chatKey 同一时间只处理一条消息(busy 锁) |
| 消息缓冲 | AI 输出期间用户新消息暂存到缓冲区,完成后自动合并发送 |
| 工具审批 | 自动批准所有工具调用 |
| 图片输入 | 支持图片/文件/语音消息解析并传入 Backend |
| 消息去重 | 跳过重复 update,丢弃 30s 前的过期消息 |
| 撤销重做 | 接入 Backend undo('last-turn') 与 redo(),精准恢复,无孤立 functionCall 风险 |
| 指令 | 说明 |
|---|---|
/new |
新建对话(清空上下文) |
/clear |
清空当前对话历史 |
/model |
查看或切换模型 |
/session |
查看或切换历史会话 |
/stop |
中止当前 AI 回复 |
/flush |
中止当前回复并立即处理缓冲消息 |
/undo |
撤销上一轮对话 |
/redo |
恢复撤销的对话 |
/help |
显示帮助 |
在 ~/.iris/configs/platform.yaml 中设置 type: telegram(或加入多平台数组),并填写 telegram.token。
发行包和仓库默认会附带 extensions/telegram/。如果手动精简目录,需保留该目录,或先执行 iris ext install-local telegram 再启用 type: telegram。
Lark 不再从 src/platforms/registry.ts 内置注册,而是由 extensions/lark/manifest.json 在启动时自动注册。运行时入口为 extensions/lark/dist/index.mjs。
实现基于飞书官方 @larksuiteoapi/node-sdk,使用 WebSocket 长连接模式。流式输出通过卡片消息 + im.message.patch 实现(非 CardKit 2.0,技术选型详见 extensions/lark/src/card-builder.ts 文件头注释)。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { appId, appSecret, showToolStatus?, verificationToken?, encryptKey? }) |
| sessionId | 私聊:lark-dm-{userOpenId};群聊:lark-group-{chatId};话题:lark-group-{chatId}-thread-{threadId} |
| 流式支持 | 支持,通过 sendCard + patchCard 实现卡片实时更新,1000ms 节流 |
| 工具状态 | 通过 tool:update 事件实时展示工具执行进度 |
| 并发控制 | 每个 chatKey 同一时间只处理一条消息(busy 锁) |
| 消息缓冲 | AI 输出期到缓冲区,完成后自动合并发送 |
| 工具审批 | 自动批准所有工具调用(飞书支持卡片按钮回调,Phase 4 可升级为交互审批) |
| 图片输入 | 支持图片/文件/音频消息解析并传入 Backend |
| 消息去重 | 跳过重复消息,丢弃 30s 前的过期消息 |
| 撤销/重做 | 接入 Backend undo('last-turn') 与 redo(),精准恢复,无孤立 functionCall 风险 |
与 Telegram 一致(/new /clear /model /session /stop /flush /undo /redo /help)。
在 ~/.iris/configs/platform.yaml 中设置 type: lark(或加入多平台数组),并填写 lark.appId 和 lark.appSecret。
发行包和仓库默认会附带 extensions/lark/。如果手动精简目录,需保留该目录,或先执行 iris ext install-local lark 再启用 type: lark。
在飞书开放平台创建自建应用,开启「机器人」能力,配置事件订阅(WebSocket 模式不需要公网 IP)。
基于 Node.js 原生 http 模块,零额外后端 Web 框架;前端为 Vue 3 + Vite。
| 项目 | 说明 |
|---|---|
| 构造参数 | (backend, { port, host, authToken?, managementToken?, configPath, ... }) |
| sessionId | 客户端传入,或自动生成 web-{uuid} |
| 流式支持 | 支持,通过 SSE 推送 delta / stream_end |
| 图片输入 | 支持文件选择、拖拽上传、剪贴板粘贴 |
| 历史回显 | 支持图片消息回显 |
| 热重载 | 通过 backend.reloadLLM() / backend.reloadConfig() 实现 |
- 最多 4 张图片,单张不超过 4MB
- 最多 3 个文档(PDF / DOCX / PPTX / XLSX 等),单个不超过 10MB
- 附件总上限 20MB
- 同时支持
application/json和multipart/form-data两种请求格式
| Backend 事件 | SSE 数据 |
|---|---|
response |
{ type: 'message', text } |
stream:start |
{ type: 'stream_start' } |
stream:chunk |
{ type: 'delta', text } |
stream:end |
{ type: 'stream_end' } |
error |
{ type: 'error', message } |
assistant:content |
{ type: 'assistant_content', message } |
done |
{ type: 'done_meta', durationMs } |
| (chat handler) | { type: 'done' } — 整个请求处理完毕 |
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/chat |
发送消息(SSE 响应),支持 JSON 和 multipart/form-data |
| GET | /api/chat/suggestions |
获取聊天快捷建议 |
| GET | /api/sessions |
列出会话 |
| GET | /api/sessions/:id/messages |
获取会话消息 |
| DELETE | /api/sessions/:id |
删除会话 |
| DELETE | /api/sessions/:id/messages?keepCount=N |
截断历史 |
| GET | /api/config |
获取配置(敏感字段脱敏)🔒 管理令牌 |
| PUT | /api/config |
更新配置(触发热重载)🔒 管理令牌 |
| POST | /api/config/models |
列出可用模型 🔒 管理令牌 |
| GET | /api/status |
服务器状态 |
| GET | /api/models |
列出可用模型 |
| POST | /api/model/switch |
切换模型 |
| GET | /api/agents |
获取运行时可用 Agent 列表 |
| GET | /api/agents/status |
获取 agents.yaml 完整状态 |
| GET | /api/deploy/state |
获取部署状态 🔒 管理令牌 |
| GET | /api/deploy/detect |
检测部署环境 🔒 管理令牌 |
| POST | /api/deploy/preview |
预览部署配置 🔒 管理令牌 |
| POST | /api/deploy/nginx |
部署 Nginx 配置 🔒 管理令牌 |
| POST | /api/deploy/service |
部署 systemd 服务 🔒 管理令牌 |
| POST | /api/deploy/sync-cloudflare |
同步 Cloudflare SSL 设置 🔒 管理令牌 |
| GET | /api/cloudflare/status |
Cloudflare 连接状态 🔒 管理令牌 |
| POST | /api/cloudflare/setup |
配置 Cloudflare 连接 🔒 管理令牌 |
| GET | /api/cloudflare/dns |
列出 DNS 记录 🔒 管理令牌 |
| POST | /api/cloudflare/dns |
添加 DNS 记录 🔒 管理令牌 |
| DELETE | /api/cloudflare/dns/:id |
删除 DNS 记录 🔒 管理令牌 |
| GET | /api/cloudflare/ssl |
获取 SSL 模式 🔒 管理令牌 |
| PUT | /api/cloudflare/ssl |
设置 SSL 模式 🔒 管理令牌 |
支持两种 Content-Type:
JSON 格式(application/json):
{
"sessionId": "web-optional-id",
"message": "请帮我看一下这张图",
"images": [
{
"mimeType": "image/png",
"data": "iVBORw0KGgoAAA..."
}
],
"documents": [
{
"fileName": "report.pdf",
"mimeType": "application/pdf",
"data": "JVBERi0xLjQ..."
}
]
}Multipart 格式(multipart/form-data):
| 字段 | 类型 | 说明 |
|---|---|---|
message |
text | 消息文本 |
sessionId |
text | 可选会话 ID |
images |
file(可多个) | 图片文件 |
documents |
file(可多个) | 文档文件 |
说明:
message、images、documents不能同时为空- JSON 格式中
images[].data为 不带前缀 的 base64 字符串,也兼容data:image/png;base64,...形式 - 支持的文档类型:PDF、DOCX、PPTX、XLSX 等(详见
media/document-extract.ts)
GET /api/sessions/:id/messages 中,图片会以如下形式返回给前端:
{
"role": "user",
"parts": [
{
"type": "image",
"mimeType": "image/png",
"data": "iVBORw0KGgoAAA..."
},
{
"type": "text",
"text": "请描述这张图"
}
]
}供 handlers/ 等内部模块调用:
| 方法 | 说明 |
|---|---|
hasPending(sessionId) |
检查是否已有进行中的 SSE 连接 |
registerPending(sessionId, res) |
注册 SSE 响应 |
removePending(sessionId) |
移除 SSE 响应 |
dispatchMessage(sessionId, message, images?, documents?, agentName?) |
调用对应 Agent 的 backend.chat() |
resolveAgent(req) |
根据 X-Agent-Name 请求头解析 Agent 上下文 |
addAgent(name, backend, config, ...) |
注册 Agent(多 Agent 模式) |
getAgentList() |
获取可用 Agent 列表(单 Agent 返回空数组) |
setMCPManager(mgr, agentName?) |
注入 MCP 管理器 |
getMCPManager(agentName?) |
获取 MCP 管理器 |
多 Agent 模式下,所有 Agent 共享一个 WebPlatform HTTP 端口,通过 X-Agent-Name 请求头路由。详见 agents.md。
CLI 模式不是一个平台适配器,而是直接调用 Backend 核心层的 headless 入口。外部传入 prompt,Iris 执行完整的 Agent 循环(LLM + 工具调用),输出结果后退出。
# 基本用法
iris -p "分析这个项目的架构"
npm run cli -- -p "分析这个项目"
# 位置参数
iris "帮我找出所有 TODO"
# 管道传入
echo "列出所有导出函数" | iris
# 多轮对话(复用 session)
iris -p "分析代码" -s my-task
iris -p "继续优化" -s my-task
# JSON 输出(供程序解析)
iris -p "列出文件" --output json
# 流式 + 工具过程
iris -p "重构代码" --stream --print-tools| 参数 | 说明 | 默认值 |
|---|---|---|
-p, --prompt <text> |
提示词(也可作为位置参数或 stdin) | 必填 |
-s, --session <id> |
会话 ID,支持多轮对话 | 自动生成 cli_YYYYMMDD_HHMMSS_xxxx |
--model <name> |
覆盖默认模型 | 配置文件中的值 |
--agent <name> |
指定 Agent(多 Agent 模式) | 第一个已定义的 Agent |
--cwd <dir> |
工具执行的工作目录 | process.cwd() |
--stream / --no-stream |
流式输出控制 | 取配置文件 |
--output <format> |
输出格式:text(默认)/ json |
text |
--print-tools |
工具调用过程输出到 stderr | false |
-h, --help |
显示帮助 | |
-v, --version |
显示版本 |
| 特性 | 说明 |
|---|---|
| 会话隔离 | 每次调用独立 sessionId,天然支持多进程并行 |
| 自动审批 | 强制 autoApproveAll: true,headless 模式不卡审批 |
| 事件驱动 | 监听 Backend 的 response / stream:chunk / tool:update / done / error 事件 |
| 退出码 | 成功返回 0,有错误返回 1 |
| 工具输出分离 | --print-tools 的输出走 stderr,不污染 stdout 的正文输出 |
{
"sessionId": "cli_20260318_143052_a7x2",
"response": "AI 的回复文本...",
"toolCalls": [{ "name": "read_file", "args": { "path": "src/index.ts" } }],
"model": "gemini-2.5-flash",
"durationMs": 3200
}splitText(text, maxLen):按最大长度分段,优先在换行处切分。供 Discord / Telegram 等受消息长度限制的平台使用。
- 创建
src/platforms/新平台名/index.ts - 继承
PlatformAdapter - 构造函数接收
backend: Backend - 在
start()中监听需要的 Backend 事件(response/stream:*/tool:update/error) - 监听用户输入并调用
backend.chat(sessionId, text) - 若平台要支持图片和文档输入,则改为
backend.chat(sessionId, text, images, documents) - 在
src/index.ts中添加 import 和 switch case