文档同步
本页由 plugins/adapters/telegram/README.md 自动生成。请修改包内 README 后运行 pnpm sync:adapter-docs。
@zhin.js/adapter-telegram
Zhin.js Telegram Bot API 适配器(Plugin Runtime),默认通过 长轮询 getUpdates 收发消息(无需 host)。
功能
- 长轮询
getUpdates入站(默认;无需公网 IP / host-http) - 解析 text / image / video / audio / voice / document / sticker / location / callback_query
- 支持私聊与群组
- 出站
send({ conversation, payload })→ Bot API(Markdown→安全 HTML / media / keyboard) - 约定式
defineAdapter/definePlugin(无需usePlugin) - Webhook 模式通过
httpHostToken注册回调路由,支持 secretToken 校验
更新确认与重投
- Polling 在
handleUpdate()的框架接纳 Promise 成功后推进本地 offset;同批次接纳失败会停止处理后续 update,保留失败 update 供下一次重投。Telegram 在下一次携带更高 offset 的getUpdates请求时才确认旧 update。 - Webhook 等待框架接纳后返回 200;请求到达时端点已关闭、尚未进入 Core 的处理被取消或接纳失败返回 503,非法 JSON/update_id 返回 400,secret 不匹配返回 403。仅 native
platform.receive成功不等于消息接纳成功,还需等待 canonicalmessage.receive。已经进入 Core 的业务由 generation lease 完成,随后成功仍返回 200,即使端点期间关闭,避免把完成的副作用错误地触发重投。 - 每个 Endpoint 最多保存 2,048 个已接纳 update_id,并合并相同 ID 的并发请求;最多允许 256 个不同 ID 的并发接纳,超过容量返回失败供重投。失败不计入已接纳集合。
- 去重状态和 polling offset 属于当前 Endpoint generation,不是耐久 checkpoint。HMR/进程重启会清空;已接纳但尚未由下一轮 offset 请求确认的尾部 update,或 webhook 200 尚未到达 Telegram 的 update,可能再次投递。当前提供至少一次重投路径,不保证跨重启恰好一次;有外部副作用的插件应使用 update/message ID 做持久幂等。
- JSON API 和 multipart/form-data 默认 30 秒 deadline;长轮询允许服务端 timeout 外加 15 秒。客户端
callApi/callApiForm第三个参数可传 AbortSignal,端点停止也会取消当前 API 请求。TelegramApiError.kind区分 timeout、cancelled、network、http、authentication、rate_limit、api、invalid_response;429 可读取retryAfterSeconds。发送失败不会自动重试,超时/网络中断的送达结果可能未知。 TelegramApiError同时遵循EndpointDeliveryError合同:明确 Bot API 拒绝为 rejected,网络/超时或异常响应为 unknown,发出请求前取消为 not_sent。一个 payload 拆成多个发送动作时,已有动作成功而后续失败会报告partial_delivery/ unknown,并在 cause 保留后续错误;不能把整条消息视为全部拒绝或安全重发。- Polling 请求失败时 transport 状态为 reconnecting,成功拉取后恢复 open;Webhook 的 open 代表本地路由与注册成功,不能证明公网代理实时可达。
确认与重试规则依据 Telegram Bot API getUpdates / setWebhook。
安装
pnpm add @zhin.js/adapter-telegramPlugin Runtime
@zhin.js/adapter— 约定式adapters/telegram/index.ts(defineAdapter)@zhin.js/core—endpoint[endpointSpec].events入站、outboundMessageToken出站zhin.js—plugin.ts(definePlugin)- 配置经插件
schema.json落到顶层<instanceKey> - 无需
@zhin.js/host-http(polling 路径)
入站:endpoint[endpointSpec].events.emit('message.receive', { conversation, message, content, sender, metadata })(conversation 为 ConversationRef,由 telegramInboundConversation 归一化)
出站:send({ conversation, payload }) → Telegram Bot API
平台权限(platform permit)
- 成员身份:群消息入站时经
getChatMember(60s 缓存)解析,写入sender.platform(见下文「成员身份」);不再写metadata.senderRole/metadata.senderPermissions。 plugin.ts在 generation setup 注册 checker,并在 dispose 注销;Plugin Runtime CapabilityIngress 与 ToolSystem 统一经 CorecanAccessTool()消费permissions。
前置条件
| 要求 | 说明 |
|---|---|
| Bot Token | 通过 @BotFather 创建并获取 Token |
| Polling(默认) | 本地/生产均可;主动拉取更新,无需公网 HTTPS |
| 网络 | 出站可访问 api.telegram.org |
| host-http | Polling 不需要;Webhook 需要 CLI HTTP Host |
必填字段(endpoints[i]):id、token。
渐进式回复
私聊默认通过 Telegram 草稿展示生成中的文本或 Markdown,完成后正式发送。草稿是临时预览,不代表消息已保存;取消只停止更新,不会自动把部分草稿正式发送。群聊和不支持的目标继续使用完整消息发送。
首段立即展示,后续更新默认至少间隔 500 毫秒。可在实例配置或单个 endpoint 中设置:
telegram:
streaming: true
streamingIntervalMs: 500
endpoints:
- id: my-telegram-bot
token: ${TELEGRAM_TOKEN}
- id: complete-replies
token: ${OTHER_TELEGRAM_TOKEN}
streaming: falsestreaming: false 关闭生成中的公开更新。草稿与正式回复保持同一话题,最终消息保留引用与交互内容。超过平台单条容量时按内容顺序接续消息,Markdown 代码块和链接保留格式与原文;图片、语音和文件仍使用现有上传发送链路。结果未知时不会自动重复正式发送。
最小配置
configVersion: 2
# zhin.config.yml(Plugin Runtime)
plugins:
telegram: "@zhin.js/adapter-telegram"
telegram:
# polling: true # 默认
endpoints:
- id: my-telegram-bot
token: ${TELEGRAM_TOKEN}主配置 plugins 映射需引用 @zhin.js/adapter-telegram(instanceKey: telegram)。
环境变量
| 变量 | 说明 |
|---|---|
TELEGRAM_TOKEN | 示例中由 YAML ${TELEGRAM_TOKEN} 引用的 Bot Token;变量名可自行定义 |
TELEGRAM_WEBHOOK_SECRET | 示例中由 YAML ${TELEGRAM_WEBHOOK_SECRET} 引用的 webhook secret;变量名可自行定义 |
Webhook
配置 polling: false,启用 CLI HTTP Host,并提供公网 HTTPS 回调地址。 webhook.path 必须与反向代理转发路径一致,多账号使用不同路径。
configVersion: 2
http:
host: 127.0.0.1
port: 8068
plugins:
telegram: "@zhin.js/adapter-telegram"
telegram:
polling: false
endpoints:
- id: my-telegram-bot
token: ${TELEGRAM_TOKEN}
webhook:
domain: https://bot.example.com
path: /telegram/webhook
secretToken: ${TELEGRAM_WEBHOOK_SECRET}启动时注册 Host 路由并调用 setWebhook;停止时释放路由。 配置 secretToken 后,回调须携带匹配的 X-Telegram-Bot-Api-Secret-Token,否则返回 403。
稳定化范围
当前为 Advanced / 首批升档候选,完整适配器测试已纳入 pnpm check:stable。 长轮询停止会中止请求与退避;即使传输层在中止后返回更新,也不会派发或推进 offset。 轮询 offset 仅保存在当前 Endpoint 内存中,不承诺跨重启去重或业务处理恰好一次。 Webhook 接收确认不等于业务处理完成;需要幂等性的业务应自行持久化去重。 详见平台稳定验收。
消息类型映射
| Telegram | 入站 content(文本摘要) | 出站 wire |
|---|---|---|
| text | 原文 | sendMessage |
| markdown | 原文 | sendMessage(parse_mode: HTML) |
| photo | [image] / caption | sendPhoto(file_id / url) |
| video | [video] | sendVideo |
| audio / voice | [audio] / [voice] | sendAudio / sendVoice |
| document | [file: name] | sendDocument |
| sticker | [sticker: …] | sendSticker |
| location | [location: lat,lon] | sendLocation |
| callback_query | [action: data] | — |
AI tools
| Kind | Path |
|---|---|
| Platform tools (10) | agents/telegram/skills/telegram-{chat-admin,group-permissions,messages}/tools/(invite / pin / admins / sticker / poll 等) |
| Skill doc | agents/telegram/skills/telegram-chat-admin/SKILL.md, agents/telegram/skills/telegram-group-permissions/SKILL.md, agents/telegram/skills/telegram-messages/SKILL.md |
Console 页面
启动后打开 Console 的「Telegram」页(地址 /telegram),可以:
- 查看运行中的连接、点「测试连接」做连通性诊断;
- 在账号库保存、修改、启动、停止、替换或删除该 Adapter 的账号;新增仅保存,不创建 SDK Client,需手动启动;
- 如果适配器要求登录,页面会显示 LoginAssist 的扫码 / 滑块 / 设备确认待办;
读取需要 telegram:read(read 级别,内置角色 viewer 及以上都有);增删改账号等写操作需要 telegram:write(operate 级别,operator 及以上都有,绑定到本适配器的角色也算),首次迁移还需管理员身份;全局所有者不受限,见插件权限的级别。页面里的「添加 endpoint」等价于原来的 telegram endpoint add。
首次修改启动配置里的既有账号前,管理员必须核对清单并确认一次迁移。原 canonical 身份与历史保留,完整账号配置加密进入 Host 数据库;此后账号管理只写数据库,不修改启动文件或 .env,不重启进程。没有持久化账号端口的环境可使用静态配置连接,动态写操作明确不可用。
启停意图在进程重启后恢复。停止或删除会取消并关闭目标账号的在途操作,其他账号继续运行;删除保留历史。已启动账号的连接配置更新先关闭旧 incarnation,再仅重连该账号;平台身份变化按替换处理,分配新内部身份、保留旧历史,新账号不继承旧 Session、个人设置或启动意图。提交结果未知时先刷新核实,不自动重复 CAS 或平台操作。
Telegram 的 token / apiBaseUrl 更新通过只读 getMe 核验真实 bot ID。核验失败或操作取消时不提交配置、不改正在运行的 Client;身份变化由统一 Adapter 账号替换流程保存为未启动的新身份。
故障排查
| 现象 | 排查 |
|---|---|
| 收不到消息 | Token 是否正确;进程已 open();同一 Token 勿多进程同时 polling |
| Polling 报错 | 检查能否访问 api.telegram.org;查看日志 op: poll |
| Webhook 配置报错 | 检查 HTTP Host、公网 HTTPS、反向代理路径与 secretToken |
| 发送失败 | Token 是否被撤销;查看 Bot API 错误描述 |
Documentation
License
MIT
禁用按钮
canonical keyboard 的 disabled: true 按钮映射为 Telegram Bot API 10.3 的 disabled: {},不携带 callback_data;启用按钮仍使用原回调数据,并保留 1–64 UTF-8 字节校验。自建 Bot API Server 需支持该版本,见 DisabledButton。
当前账号资料
资料端口使用 Bot API 的账号方法,与聊天管理方法分开:getMyName / setMyName 读取及修改昵称,getMyShortDescription / setMyShortDescription 处理简短签名(signature),getMyDescription / setMyDescription 处理个人简介(bio)。这些操作针对默认语言版本;修改沿用 Agent 现有的身份类审批。
setMyProfilePhoto 通过 multipart 附件上传已解析的 JPEG 字节,不把 URL 冒充上传文件,也不向模型暴露含 Bot Token 的 Telegram 下载地址。资料端口不返回需凭据访问的文件接口 URL,亦不声明 Bot 的年龄、性别、生日及兴趣可写。
nickname / profile 配置只补足缺失字段;Endpoint 字段优先于适配器默认值,不会修改 Telegram 账号。接口依据 Bot API 官方文档及头像设置方法。
成员身份
群消息经 getChatMember 归一为 sender.platform:creator → owner(带全部管理员权限);administrator → admin,其 can_* 权限去掉前缀进 permissions(如 restrict_members、pin_messages);member 与仍在群内的 restricted → member(restricted 的 can_* 同样上报);left / kicked → guest。Telegram 没有身份组,roles 不上报;私聊不查询、不上报;查询失败时不上报。能力画像:group.scene.platformStanding = { standing: true, permissions: true }。详见 ADR 0056 §1。