
用 copilotkit/channels-telegram 构建 Telegram AI 机器人AG-UI 渠道适配器完整指南【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit本文基于仓库中的packages/channels-telegram编写。copilotkit/channels-telegram是 CopilotKit JSX Channels 生态中专用于 Telegram 的PlatformAdapter它通过 grammY 接入 Telegram 更新长轮询或 Webhook把copilotkit/channels-ui的 JSX 组件树渲染成 Telegram Bot API 可识别的 HTML 消息并借助分块编辑流实现近似流式输出、通过callback_query实现交互与人工介入HITL。读完本文你将能把一个用 JSX 声明一次 UI 的 AG-UI Agent 接入 Telegram 机器人DM、普通群、论坛话题三种会话形态理解渲染映射、消息长度预算、交互确认与 HITL 的底层实现并避开 v1 的已知边界。1. 它在 Channels 生态中的定位CopilotKit Channels 的思路是一次编写 UI多端复用你用copilotkit/channels-ui的 JSX 词汇表Message、Section、Actions、Select等描述界面用copilotkit/channels驱动机器人逻辑真正与 Telegram 通信的只有copilotkit/channels-telegram这一个包。它实现PlatformAdapter接口adapter.ts负责ingress接收 Telegram 更新长轮询或 Webhook翻译成 AG-UI 引擎能消费的事件onTurn/onCommand/onInteraction/onReaction/onThreadStartedegress把 JSX 组件树渲染为 Telegram 消息HTML 解析模式 可选内联键盘 可选照片流式与交互通过限频的editMessageText实现近似流式通过answerCallbackQuery先行确认交互、再解码为InteractionEvent交给引擎。adapter 自己持有 Telegram bot token在托管路径下Channel 运行在由 CopilotKit Intelligence 配置的CopilotRuntime内提供免费额度由 Runtime 负责启动并拥有 Channel 的生命周期你也可以基于 SDK 原语自建 Channel runnerREADME 明确说明这是一种受支持的方式。从 package.json 可见其核心依赖只有五个grammyTelegram Bot 框架、copilotkit/channels-core渠道抽象、copilotkit/channels-uiJSX 词汇表、ag-ui/client与zod。2. 安装在 pnpm 工作区中执行pnpm add copilotkit/channels-telegram copilotkit/channels copilotkit/channels-ui三者分工明确copilotkit/channels-telegram是 Telegram 适配器本包copilotkit/channels是创建 Channel 的入口copilotkit/channels-ui提供 JSX 组件词汇表。依赖copilotkit/channels-core与copilotkit/channels-ui均以workspace:^形式引用见 package.json即与本仓库同源构建。3. 快速开始3.1 前置文件必须是.tsxJSX 工厂指向 channels-uiJSX 在 TypeScript 中需要配置 JSX factory。在tsconfig.json中指向copilotkit/channels-ui{ compilerOptions: { jsx: react-jsx, jsxImportSource: copilotkit/channels-ui } }这样Message、Section等组件会被编译为该词汇表定义的节点对象ChannelNode而不是 React 元素。3.2 完整示例import { createChannel } from copilotkit/channels; import { telegram, defaultTelegramTools, defaultTelegramContext, } from copilotkit/channels-telegram; import { Message, Section } from copilotkit/channels-ui; import { CopilotRuntime, CopilotKitIntelligence } from copilotkit/runtime/v2; import { createCopilotNodeListener } from copilotkit/runtime/v2/node; const bot createChannel({ identifyUser: platform, name: support-bot, // project-unique Intelligence Channel name adapters: [ telegram({ token: process.env.TELEGRAM_BOT_TOKEN!, }), ], agent: (threadId) makeAgent(threadId), tools: [...defaultTelegramTools, ...appTools], // lookup_telegram_user your tools context: [...defaultTelegramContext, ...appContext], // tagging/HTML/thread guidance }); bot.onMention(({ thread }) thread.runAgent()); // Optional: greet users when they start a DM bot.onThreadStarted(async ({ thread }) { await thread.post( Message SectionHi! How can I help?/Section /Message, ); }); // The runtime owns the channels lifecycle — there is no bot.start(). const runtime new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to cloud-hosted CopilotKit Intelligence — override // both together only for a self-hosted deployment. apiKey: process.env.CPK_INTELLIGENCE_API_KEY!, // free tier available }), channels: [bot], }); // Creating the listener starts the Channels connection. const listener createCopilotNodeListener({ runtime }); // Optional: await that activation so a broken config fails startup loudly. await listener.channels.ready(); // listener.channels.stop() tears it down要点说明telegram(opts)返回一个TelegramAdapter。默认运行在long-polling模式——不需要公网 URL。设置mode: webhook配合webhook.domain可通过 HTTP 接收更新mode: auto则让适配器依据环境变量自行选择在 Vercel/Lambda 等 serverless 环境优先 Webhook否则回退到轮询。没有bot.start()Channel 的生命周期由 Runtime 拥有。createCopilotNodeListener({ runtime })一创建即开始建立 Channel 连接listener.channels.ready()可等待激活完成让配置错误在启动时就大声失败而不是运行时才暴露。3.3 必填环境变量变量用途TELEGRAM_BOT_TOKEN来自 BotFather 的机器人 token形如123:ABC-xyzCPK_INTELLIGENCE_API_KEYCopilotKit Intelligence API Key托管路径提供免费额度3.4 适配器构造参数从 types.ts 可以看到TelegramAdapterOptions的完整字段字段类型说明tokenstringBotFather 提供的 bot token必填modepolling \| webhook \| auto更新接收方式默认pollingwebhook{ domain, path?, port?, secretToken? }Webhook 配置mode为webhook或auto且提供 domain 时必填interruptEventNamesReadonlySetstring应当中断运行中 Agent 的 AG-UI 事件名showToolStatusbooleanAgent 运行时展示 using tool… 状态消息greetingstring用户开启会话时发送的问候语suggestedPrompts{ title, message }[]会话开始时的建议 prompt 卡片其中webhook子字段domain公开域名、path回调路径默认/telegram、port本地 HTTP 服务端口默认 8443见下文、secretTokenWebhook 安全令牌。4. Ingress 模式polling / webhook / auto模式工作方式polling默认。grammY 长轮询无需公网 URLwebhookgrammY webhook 最小 Node HTTP 服务器需要webhook.domainauto当设置了VERCEL/AWS_LAMBDA_FUNCTION_NAME/NETLIFY时用 Webhook否则用轮询源码层面的细节值得注意adapter.ts 的resolveModeauto只有在serverless环境变量存在且配置了webhook.domain时才选择 Webhook否则回退到轮询——因为如果选了 Webhook 却没有域名startWebhook()会直接抛错这与auto文档中回退到轮询的语义矛盾。显式mode: webhook而没有 domain 仍会抛错因为这是真正的配置错误。Webhook 模式内部adapter.ts调用bot.api.setWebhook(url, { secret_token })注册起一个最小的 Node HTTP 服务器把请求喂给 grammY 的webhookCallback非回调路径返回 404端口默认 8443Telegram 只向 443/80/88/8443 交付 webhook 更新临时端口0无法收到投递服务器error事件EADDRINUSE/EACCES会显式拒绝start()的 Promise而不是变成隐蔽的未捕获异常。优雅停机顺序stop()adapter.ts也经过了精心设计先deleteWebhook让 Telegram 停止投递再关闭本地 HTTP 服务器避免在途 POST 命中已关闭的 socket最后bot.stop()。反过来的顺序会在关闭服务器→删除 Webhook之间留下 Telegram 持续向已关闭 socket POST 的空窗。5. JSX → Telegram HTML 渲染与消息预算renderTelegram(ir)render/telegram.ts把copilotkit/channels-ui的组件树翻译成一个 Telegram Bot API 载荷textparseMode: HTML可选inlineKeyboard、可选photos。5.1 组件映射表JSX 组件Telegram 渲染结果Message容器扁平化其子节点Headerb粗体标题原始文本截断到 256 字符Section/MarkdowntelegramHtml()转换后的内联 HTMLField(s)有 label 时渲染为blabel/b value单文本子节点时整体加粗Contexti斜体Actions内联键盘行URL 按钮优先否则 callback 按钮Select内联键盘行选项即按钮Imagephoto附 caption由alt提供Tablepre等宽网格自动计算列宽、padEnd对齐、两空格分隔Divider──────Input占位文本(open the chat to type your answer)未知组件类型静默跳过渲染器是 total 的不会抛错5.2 Telegram API 限制与预算机制限制集中在TELEGRAM_LIMITSrender/budget.ts限制值作用于messageText4096每条消息字符数caption1024图片 caption 字符数callbackData64每个callback_data的字节数buttonsPerRow8每行内联键盘按钮数buttonsPerMessage100整条消息的内联键盘按钮总数buttonText64按钮标签字符数photosPerMessage10每条消息照片数配套工具函数同样在 budget.tstruncateText(text, max)截断到max字符若被截断则追加…标记返回值永不超过maxclampArray(items, max)裁剪数组并返回{ items, overflow }byteLen(s)用Buffer.byteLength(s, utf8)计算 UTF-8 字节数——这是callback_data64字节限制的正确度量。5.3 预算机制先限原始文本再迭代收敛 HTML一个容易被忽略但至关重要的实现细节render/telegram.ts预算按原始字符测量但发出的载荷是 HTML。b、a href以及amp;/lt;实体展开都会增加长度——接近 4096 原始字符、带有大量标记的消息转换后可能超过 Telegram 的硬限制报message is too long且格式回退不会捕获该错误。因此渲染器先把原始文本截到预算内若转换后的 HTML 仍超限按超量比例迭代收缩原始预算每次至少减去overshoot 16最多 32 轮快速收敛仍有超限病态的高标记密度时最后一道硬保险sliceHtmlSafely在绝不劈开标签或实体的安全边界处切片。另外行级包装信息b/i/pre/none在渲染时被记录在每个行条目上当最后一行为预算被截断时可确定性地重新包装而不是靠正则从 HTML 反推结构。5.4 按钮预算与 callback_data 降级appendButtonsrender/telegram.ts对整条消息的键盘累计计数而非每个actions/select节点单独计超出 100 个按钮的部分静默丢弃——否则 Telegram 会拒绝整条消息。callback_data超过 64 字节的按钮/选项会被静默降级跳过返回null被 filter 掉而不是抛错保住消息其余部分。Select选项使用id属性若未提供 id 则JSON.stringify(value)作为 callback data——选项值是大对象时请给Option使用短的id字符串。6. 流式输出ChunkedEditStreamTelegram 没有服务端推送流式能力copilotkit/channels-telegram用限频编辑来近似先发一条占位消息然后随着 token 到达逐次editMessageText更新每条消息每秒钟至多编辑一次minIntervalMs默认 1000ms。当回复接近 Telegram 的 4096 字符硬限制时软限制默认取TELEGRAM_LIMITS.messageText / 2 2048流会透明地创建第二条消息继续输出——已冻结的分块边界不会回流每条 Telegram 消息始终保持在限制内。ChunkedEditStreamchunked-edit-stream.ts是一个按消息分块的文本状态机核心设计append(fullText)接收到目前为止的完整累积文本由流自己决定哪些切片属于哪条 Telegram 消息分块边界一旦发布即冻结refreezeBoundaries只在活动块超过软限制时从lastFrozen向后冻结新边界断点优先取窗口内最后一个换行、其次最后一个空格都没有则硬切并带minAdvance下限防止对抗性输入产生 1 字符碎块每条消息一个内部EditStream带缓冲、串行队列与节流定时器只有编辑成功后才推进posted指针瞬时失败会留给下一次 flush 重试transform即telegramHtml在每次editAt之前应用占位文本用纯文本…避免 HTML 解析模式下的 Markdown 下划线渲染问题finish()等待所有挂起编辑完成终态编辑失败会把错误抛给调用方adapter 中记录日志而不中断响应。软限制取 2048 是为了给 HTML 实体展开留出余量→amp;放大 5 倍/→lt;/gt;放大 4 倍但源码注释明确这是针对典型文本的风险降低启发式并非硬保证——对抗性的全实体输入2048 个→ 约 10240 个转换字符仍可能超限。7. 交互处理先确认ack-first每次内联键盘按钮点击callback_query都会先及时answerCallbackQuery——适配器的ackDeadlineMs是 3 秒客户端转圈会很快消失远在 Telegram 约 30 秒的answerCallbackQuery有效窗口内。确认之后再decodeInteraction提取会话键与铸造的 opaque id交给引擎生成InteractionEvent。源码细节listener.tsack 被包在自己的 try/catch 中——ack 失败如过期按钮 →query is too old绝不能阻塞后续的解码与分发否则awaitChoice的等待者会被永久搁浅decodeInteractioninteraction.ts同时接受 grammY 包装的 update 和裸 callback query 对象没有data或没有message的点击会解码失败返回undefined由机器人无害地忽略——不相干的点击不会产生任何效果。8. 人工介入HITL与 ActionStore用thread.awaitChoice(Picker .../)发一个交互式内联键盘并阻塞直到某次点击解析它解析出的值就是被点击按钮的 callback data。Agent 中断on_interrupt由 run renderer 捕获并分发到你的onInterrupt处理器处理器发一个 picker点击后通过thread.resume(value)恢复 Agent。一个重要的群聊语义在非论坛群中每个会话按发送者键控见已知限制所以为某个用户发的内联键盘提示只有该用户点击才会解析其他群成员点击同一按钮会被 ack 但不解析原用户的待定选择。9./start→onThreadStartedlistener 拦截私聊中的 Telegram/start命令并触发onThreadStartedlistener.ts让机器人在第一轮对话前先发问候或配置会话。实现上bot.command(start)处理器仅在chat.type private时生效文本消息处理器会对bot_command实体做专门解析支持/cmd与/cmdbot形式但遇到start会提前 return避免onCommand(start)与onThreadStarted对同一条/start消息双重分发。10. 文件进出入站照片、音频、视频、文档等附件会被下载并作为多模态 AG-UI content parts 交付给 AgentbuildFileContentParts。listener 分别挂载了message:photo/message:document/message:video/message:audio/message:voice处理器listener.ts照片取尺寸最大的那张默认按image/jpeg处理Telegram 会把多数照片重编码为 JPEG只有确定该轮会被回答私聊、被 提及或回复机器人才下载文件避免为不相关的群消息付出昂贵下载。回复reply另一条带附件的消息时被引用的消息内容会折叠进本轮上下文如回复旧图片并问里面是什么。出站thread.postFile({ bytes, filename })以document类型发送文件adapter.ts。11. 内置工具与上下文11.1defaultTelegramToolslookup_telegram_user随包附送的工具built-in-tools.ts让 Agent 能把公开username解析成 Telegram user id 用于 提及。它调用getChat并只支持公开username查询不以开头时立即返回undefined任意显示名/真名搜索不被支持。成功时返回found: true及mention字符串username或tg://user?id…可直接原样放进回复里 人失败时返回found: false应写普通名字。通过tools: [...defaultTelegramTools, ...appTools]展开进配置。11.2defaultTelegramContext让 LLM 懂 Telegram 的规则三份上下文条目built-in-context.ts每个也可单独按名引用telegramTaggingContext——必守流程用户要求 人时必须先调用lookup_telegram_user拿到用户名就写username拿不到就写纯显示名绝不臆造 handletelegramFormattingContext——写标准 Markdown 即可桥会自动转成 Telegram HTML不要预先手写b/i/code/a href等原始 HTML否则双重编码会破坏输出列表-/*渲染为普通缩进文本Telegram 没有原生列表元素telegramConversationModelContext——三种会话形态的线程模型DM 是单一扁平对话论坛超群按 topic 各自独立上下文不跨 topic 泄漏普通群只在被 提及或回复机器人时参与上下文限定在该提及触发的回复链内。12. 命令菜单setMyCommandsregisterCommands(specs)调用bot.api.setMyCommands注册 Telegram UI 中可见的命令菜单listener 把每条 bot 命令转发给引擎的onCommand处理器。实现上有两个值得注意的适配adapter.tsTelegram 命令名限定为1–32 个[a-z0-9_]字符不允许连字符与 Slack/Discord 不同且只要有一个名字非法整个setMyCommands调用会以400 BOT_COMMAND_INVALID被拒。因此连字符会被转成下划线/file-issue注册为/file_issue引擎的normalizeCommandName也会把-→_收进来的/file_issue仍能命中file-issue处理器转换后仍非法空格、其他标点、超长的名字会被跳过并打警告而不是让整次调用失败空数组会直接跳过调用空数组会清空全部命令。13. Reactions表情回应message_reaction更新会被自动启用适配器导出TELEGRAM_ALLOWED_UPDATES订阅的完整更新类型列表message、edited_message、callback_query、message_reaction见 adapter.ts长轮询start()会自动传入。群聊机器人必须是管理员才能收到message_reaction事件私聊和频道无需额外权限Webhook 部署需要把同一列表传给setWebhookimport { TELEGRAM_ALLOWED_UPDATES } from copilotkit/channels-telegram; await bot.api.setWebhook(url, { allowed_updates: [...TELEGRAM_ALLOWED_UPDATES], });解码层interaction.ts 的decodeReaction把一次message_reaction更新展开为零个或多个IncomingReaction事件——对比old_reaction与new_reaction集合每个新增/移除的 emoji 各产出added: true/false事件只考虑type emoji条目custom_emoji被忽略。同时有环路防护机器人自己的反应来自setMessageReaction出站的回显会被忽略避免把出站反应误当作用户反应。14. v1 明确不做什么README 的边界声明与源码能力位一一对应capabilitiesadapter.ts能力v1 状态说明Modals / 原生表单提交不支持Telegram 没有 modal 表面多步表单必须靠对话驱动openModal解析为{ ok: false }引擎因supportsModals: false直接关闭该方法原生 ephemeral 消息不支持Telegram 没有仅单个用户可见的消息supportsEphemeral: false。用thread.postEphemeral(user, ui, { fallbackToDM: true })以私聊 DM 作为回退DM 要求用户此前至少给机器人直接发过一条消息否则sendMessage失败且postEphemeral解析为{ ok: false }而非抛错原生流式不支持无服务端推送用限频editMessageText近似持久化会话存储不支持TelegramConversationStore是内存实现每会话最多保留 200 条消息见 conversation-store.ts重启即丢失多 bot 安装不支持每个 adapter 实例一个 bot tokenSelect选项值往返有 64 字节上限选项的value/id序列化超过 64 字节时该选项被静默降级按钮不出现大对象值请用短的Option id其余能力位supportsTyping: true通过sendChatAction发typing状态、supportsReactions: true、supportsStreaming: true、supportsThreadTitle: true仅论坛 topiceditForumTopic、supportsSuggestedPrompts: falseTelegram 没有类似 Slack 的固定 prompt 面板setSuggestedPrompts返回unsupported。15. 已知限制使用前必读群聊会话模型普通非论坛群中bot 按user:userId键控每个会话——每位成员的 提及各自构成一条持续对话按钮点击解析到点击者自己的会话bot不维护共享群线程。论坛超群用topic:threadId按主题键控DM 是单一扁平会话scope 为dm。键控逻辑集中在deriveConversationKeyinteraction.ts一个反直觉的细节非论坛超群里 Telegram 也会在回复消息上设置message_thread_id它兼任回复线程 id因此只有在chat.is_forum时才按 topic 键控否则每个回复都会错误地开启新会话。update()不换媒体thread.update(ref, ir)只调editMessageText更新文本与内联键盘原消息附带照片不会被修改图片-only 的 IR无文本会直接 no-op因为editMessageText传空文本会触发message text is empty错误格式回退不捕获照片 ref 上编辑文本则报there is no text in the message to edit。入站文件超过 TelegramgetFile大小上限的大文件会被跳过并在其位置附带说明。lookup_telegram_user仅限username非开头的查询立即返回undefined。群内 HITL 按用户见第 8 节。并发内存会话存储不对同一会话的并发轮做串行化同会话快速连发的消息可能交错。对典型用途可接受持久化/加锁存储不在 v1 范围内。16. 公共导出 API 总览从 index.ts 导出的完整表面README Exports 一节适配器telegram工厂函数、TelegramAdapter、TelegramAdapterOptions渲染createRunRenderer、CreateRunRendererArgs、renderTelegram、telegramHtml、escapeHtml、stripHtml、withTelegramFormatFallback交互decodeInteraction、decodeReaction、conversationKeyOf、deriveConversationKey、toProviderActor预算TELEGRAM_LIMITS、truncateText、clampArray、byteLen内置资产defaultTelegramTools、lookupTelegramUserTool、defaultTelegramContext、telegramTaggingContext、telegramFormattingContext、telegramConversationModelContext流式与存储ChunkedEditStream、ChunkedEditStreamConfig、TelegramConversationStoreListenerattachTelegramListener、ListenerConfig、TELEGRAM_ALLOWED_UPDATES文件buildFileContentParts、TelegramFileRef、AgentContentPart、FileDeliveryConfig类型/常量ConversationKey、ReplyTarget、TelegramMessageRef、TelegramInlineButton、TelegramPayload、DM_SCOPE。17. 测试与进一步探索本包配备了完整的 vitest 测试套件packages/channels-telegram/src/tests/覆盖渲染render/tests/telegram.test.ts、budget.test.ts、交互解码interaction.test.ts、流式状态机chunked-edit-stream.test.ts、能力位capabilities.test.ts、内置工具与上下文、ephemeral 回退、反应解码、格式回退、下载文件等可作为行为契约参照。运行测试pnpm --filter copilotkit/channels-telegram test从adapter.ts的入口类、listener.ts的更新分发、interaction.ts的键控与解码到render/telegram.ts的预算渲染整个链路都遵循构造无副作用、start 才联网adapter.ts与更新处理异常不击穿轮询循环bot.catch错误处理器adapter.ts两条工程原则——前者让测试注入 fake bot 成为可能后者保证一条坏更新被消费offset 前进而不是无限重投的毒丸循环进程也不会静默退出。理解这些设计能帮你更稳地在上层定制自己的 Telegram Agent。【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考