Appearance
OpenCode 通知系统:Bun + Telegram Bot 集成
执行摘要
本研究探讨了为 OpenCode 实现一个通知系统,当以下情况时通过 Telegram 发送实时状态更新:
- OpenCode 成功完成执行
- OpenCode 执行失败
- OpenCode 需要用户确认/交互
建议的方案利用 Bun 作为运行时,因其性能和 WebSocket 能力,结合 Telegram Bot API 实现可靠、跨平台的消息传递。这种方法提供了清晰的关注点分离,允许 OpenCode 发出事件,而独立的通知服务处理消息传递。
技术分析
1. 架构概述
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ OpenCode │────────▶│ Notification │────────▶│ Telegram │
│ (事件发射器) │ 事件 │ 服务 (Bun) │ HTTP │ Bot API │
└─────────────────┘ └──────────────────┘ └─────────────┘
│
▼
┌──────────────┐
│ 本地 IPC/ │
│ WebSocket │
└──────────────┘核心组件:
- OpenCode:发出生命周期事件(开始、成功、失败、需要确认)
- 通知服务 (Bun):订阅事件、格式化消息、处理 Telegram API
- Telegram Bot:向用户设备传递格式化的通知
2. 事件类型
| 事件名称 | 触发条件 | 载荷结构 |
|---|---|---|
opencode:start | 任务执行开始 | { taskId, command, timestamp } |
opencode:success | 任务成功完成 | { taskId, duration, outputSummary } |
opencode:failure | 任务失败 | { taskId, error, stackTrace, exitCode } |
opencode:confirm | 需要用户确认 | { taskId, message, options } |
3. Telegram Bot API 集成
端点: https://api.telegram.org/bot<token>/<method>
关键方法:
sendMessage:发送文本消息sendMessage带reply_markup:用于确认的交互式按钮sendDocument:用于大型输出/日志(可选)
消息格式:
markdown
✅ 任务完成
任务: <command>
耗时: <time>
输出: <summary>
[查看详情]4. Bun 优势
- 性能:I/O 操作比 Node.js 快 3 倍
- 内置 WebSocket:原生支持实时事件流
- TypeScript 支持:一流的 TypeScript 集成
- 打包器:内置打包功能,便于部署
5. 配置管理
环境变量:
env
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
OPENCODE_SOCKET_PATH=/tmp/opencode.sock
LOG_LEVEL=info实现指南 / 概念验证
1. Telegram Bot 设置
步骤 1:通过 BotFather 创建 Bot
bash
# 在 Telegram 上向 @BotFather 发送 /newbot
# 按照提示获取您的 bot token步骤 2:获取 Chat ID
bash
# 向您的 bot 发送一条消息,然后:
curl https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates2. 通知服务 (Bun)
typescript
// 示例:notification-service.ts
import { Serve } from 'bun';
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!;
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID!;
interface OpenCodeEvent {
type: 'success' | 'failure' | 'confirm' | 'start';
taskId: string;
timestamp: number;
data: any;
}
async function sendTelegramMessage(text: string, replyMarkup?: any) {
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: TELEGRAM_CHAT_ID,
text,
parse_mode: 'Markdown',
reply_markup: replyMarkup
})
});
return response.json();
}
function formatEventMessage(event: OpenCodeEvent): string {
const emoji = {
success: '✅',
failure: '❌',
confirm: '❓',
start: '🚀'
};
switch (event.type) {
case 'success':
return `${emoji.success} *OpenCode 任务完成*\n\n` +
`任务 ID: \`${event.taskId}\`\n` +
`耗时: ${event.data.duration}s\n` +
`输出预览: \`${event.data.output.substring(0, 100)}...\``;
case 'failure':
return `${emoji.failure} *OpenCode 任务失败*\n\n` +
`任务 ID: \`${event.taskId}\`\n` +
`错误: \`${event.data.error}\`\n` +
`退出码: ${event.data.exitCode}`;
case 'confirm':
return `${emoji.confirm} *需要操作*\n\n` +
`任务 ID: \`${event.taskId}\`\n` +
`${event.data.message}`;
case 'start':
return `${emoji.start} *OpenCode 任务开始*\n\n` +
`任务 ID: \`${event.taskId}\`\n` +
`命令: \`${event.data.command}\``;
}
}
function createConfirmationButtons(taskId: string, options: string[]) {
return {
inline_keyboard: options.map((opt, idx) => [
{
text: opt,
callback_data: JSON.stringify({ action: 'confirm', taskId, choice: idx })
}
])
};
}
// 用于 OpenCode 事件的 WebSocket 服务器
const server = Bun.serve({
port: 3000,
fetch(req) {
if (req.url.endsWith('/events')) {
return new Response('OpenCode 通知服务运行中', {
headers: { 'Content-Type': 'text/plain' }
});
}
return new Response('未找到', { status: 404 });
}
});
console.log(`通知服务运行在端口 ${server.port}`);
// 事件处理程序(概念验证)
async function handleOpenCodeEvent(event: OpenCodeEvent) {
const message = formatEventMessage(event);
if (event.type === 'confirm' && event.data.options) {
const keyboard = createConfirmationButtons(event.taskId, event.data.options);
await sendTelegramMessage(message, keyboard);
} else {
await sendTelegramMessage(message);
}
}
// 示例:处理来自 OpenCode 的传入事件
// 在生产环境中,这将连接到 OpenCode 的事件发射器3. OpenCode 事件发射器集成
概念集成模式:
typescript
// 示例:opencode-integration.ts
import { EventEmitter } from 'events';
class OpenCodeNotifier extends EventEmitter {
private notificationServiceUrl: string;
constructor(notificationServiceUrl: string) {
super();
this.notificationServiceUrl = notificationServiceUrl;
}
async notifySuccess(taskId: string, duration: number, output: string) {
const event = {
type: 'success' as const,
taskId,
timestamp: Date.now(),
data: { duration, output }
};
await this.sendToNotificationService(event);
}
async notifyFailure(taskId: string, error: string, exitCode: number) {
const event = {
type: 'failure' as const,
taskId,
timestamp: Date.now(),
data: { error, exitCode }
};
await this.sendToNotificationService(event);
}
async requestConfirmation(taskId: string, message: string, options: string[]) {
const event = {
type: 'confirm' as const,
taskId,
timestamp: Date.now(),
data: { message, options }
};
await this.sendToNotificationService(event);
return this.waitForConfirmation(taskId);
}
private async sendToNotificationService(event: any) {
await fetch(`${this.notificationServiceUrl}/event`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event)
});
}
private async waitForConfirmation(taskId: string): Promise<number> {
return new Promise((resolve) => {
this.once(`confirmed:${taskId}`, (choice: number) => {
resolve(choice);
});
});
}
}4. Telegram 回调处理
typescript
// 示例:telegram-webhook.ts
import { Serve } from 'bun';
async function handleCallback(callback: any) {
const data = JSON.parse(callback.callback_query.data);
if (data.action === 'confirm') {
// 将确认发送回 OpenCode
await fetch('http://localhost:3000/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
taskId: data.taskId,
choice: data.choice
})
});
// 回答回调查询
await fetch(
`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/answerCallbackQuery`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
callback_query_id: callback.callback_query.id,
text: '已确认!'
})
}
);
}
}
// Telegram 回调的 Webhook 服务器
const webhookServer = Bun.serve({
port: 3001,
async fetch(req) {
if (req.method === 'POST' && req.url.endsWith('/webhook')) {
const callback = await req.json();
await handleCallback(callback);
return new Response('OK');
}
return new Response('未找到', { status: 404 });
}
});
console.log(`Telegram webhook 运行在端口 ${webhookServer.port}`);5. 部署考虑
选项 A:本地开发
bash
# 运行通知服务
bun run notification-service.ts
# 设置 webhook(可选,用于回调)
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://your-domain.com/webhook"选项 B:Docker 部署
dockerfile
# 示例:Dockerfile
FROM oven/bun:1-alpine
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install
COPY . .
EXPOSE 3000 3001
CMD ["bun", "run", "notification-service.ts"]yaml
# 示例:docker-compose.yml
version: '3.8'
services:
notification-service:
build: .
ports:
- "3000:3000"
- "3001:3001"
environment:
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
restart: unless-stopped6. 安全考虑
- Token 安全:将 bot tokens 存储在环境变量中,永远不要提交到 git
- 身份验证:添加 API 密钥以验证 OpenCode → 通知服务的通信
- 速率限制:Telegram API 有速率限制(每秒 30 条消息)
- 输入清理:清理所有 markdown 输出以防止注入攻击
考虑的替代方案
| 方法 | 优点 | 缺点 |
|---|---|---|
| Telegram Bot(推荐) | 跨平台、可靠、丰富的消息格式 | 需要 bot token 设置 |
| Discord Webhook | 开发人员熟悉、丰富的嵌入 | Discord 特定 |
| 邮件通知 | 通用、异步 | 传递较慢、交互性较差 |
| 推送通知 (Pushover) | 简单、快速 | 需要付费服务 |
| Slack 集成 | 专业、面向团队 | 需要 Slack 工作区 |
挑战与缓解措施
挑战 1:确认延迟
问题:Telegram 回调可能无法实时到达 OpenCode 解决方案:实施轮询回退或 WebSocket 双向通信
挑战 2:大型输出处理
问题:任务输出可能超过 Telegram 的 4096 个字符限制 解决方案:
- 使用 "..." 指示符截断
- 使用
sendDocument发送完整日志 - 提供外部存储链接
挑战 3:多用户支持
问题:多个用户使用不同的 Telegram 账户 解决方案:
- 用户映射表 (userId → telegramChatId)
- 用户特定的 bot 命令 (
/register <userId>) - 会话管理
结论
建议的使用 Bun 和 Telegram Bot API 的通知系统为实时 OpenCode 状态更新提供了强大、高性能和用户友好的解决方案。该架构提供了:
✅ 低延迟:Bun 的性能 + WebSocket 通信 ✅ 丰富的交互性:用于确认的内联按钮 ✅ 跨平台:在 Telegram 桌面、移动端和 Web 上运行 ✅ 关注点分离:通知服务与 OpenCode 核心解耦 ✅ 可扩展性:易于添加更多事件类型或通知渠道
推荐实施路径:
- 从基本成功/失败通知开始 (MVP)
- 添加确认工作流
- 实施重试逻辑和错误处理
- 添加速率限制和输出截断
- 使用 Docker 进行生产部署
下一步:
- 设置 Telegram bot 并获取 tokens
- 在 Bun 中实现通知服务
- 将事件发射器集成到 OpenCode
- 使用各种任务场景进行测试
- 部署并监控性能