abbasyk60 commited on
Commit
5fc279e
Β·
1 Parent(s): 0b6cad1

Fix Telegram bot to call Zen API directly

Browse files
Files changed (1) hide show
  1. bot.js +147 -67
bot.js CHANGED
@@ -1,87 +1,159 @@
1
  const { Bot } = require("grammy");
2
 
3
- // Get environment variables
4
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
5
  const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
6
- const OPENCODE_URL = process.env.OPENCODE_URL || "http://localhost:7860";
7
 
8
  if (!BOT_TOKEN) {
9
  console.error("[ERROR] TELEGRAM_BOT_TOKEN not set");
10
  process.exit(1);
11
  }
 
 
 
 
12
 
13
- // Create bot
14
  const bot = new Bot(BOT_TOKEN);
15
 
16
- // Store sessions
17
- const sessions = new Map();
18
 
19
- // Helper: Call OpenCode API
20
- async function callOpenCode(message, sessionId) {
21
- try {
22
- // Create a new session or use existing
23
- const sessionResponse = await fetch(`${OPENCODE_URL}/session`, {
24
- method: "POST",
25
- headers: { "Content-Type": "application/json" },
26
- body: JSON.stringify({}),
27
- });
28
-
29
- if (!sessionResponse.ok) {
30
- throw new Error("Failed to create session");
31
- }
32
 
33
- const session = await sessionResponse.json();
34
- const sid = session.id || sessionId;
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- // Send message to OpenCode
37
- const response = await fetch(`${OPENCODE_URL}/session/${sid}/message`, {
38
- method: "POST",
39
- headers: { "Content-Type": "application/json" },
40
- body: JSON.stringify({ content: message }),
41
- });
42
 
43
- if (!response.ok) {
44
- throw new Error("Failed to send message");
45
- }
 
46
 
47
- const result = await response.json();
48
- return result.content || result.message || "No response from OpenCode";
49
- } catch (error) {
50
- console.error("[ERROR] OpenCode API call failed:", error.message);
51
- return `Error communicating with OpenCode: ${error.message}`;
 
 
52
  }
 
53
  }
54
 
55
- // Handle /start command
56
  bot.command("start", async (ctx) => {
 
 
 
 
57
  await ctx.reply(
58
- "Welcome to OpenCode AI Agent!\n\n" +
59
- "Send me any message and I'll respond using AI.\n\n" +
60
  "Commands:\n" +
61
- "/start - Show this message\n" +
62
- "/help - Show help\n" +
63
- "/new - Start a new session"
 
 
 
64
  );
65
  });
66
 
67
- // Handle /help command
68
  bot.command("help", async (ctx) => {
69
  await ctx.reply(
70
- "OpenCode AI Agent Help\n\n" +
71
- "Simply send me a message and I'll process it using OpenCode AI.\n\n" +
72
- "Features:\n" +
73
- "- AI-powered responses\n" +
74
- "- Code assistance\n" +
75
- "- Multiple AI models available\n\n" +
76
- "Use /new to start a fresh conversation."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  );
78
  });
79
 
80
- // Handle /new command
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  bot.command("new", async (ctx) => {
82
  const chatId = ctx.chat.id;
83
- sessions.delete(chatId);
84
- await ctx.reply("New session started. Send me a message!");
 
 
 
 
85
  });
86
 
87
  // Handle text messages
@@ -89,31 +161,38 @@ bot.on("message:text", async (ctx) => {
89
  const chatId = ctx.chat.id;
90
  const message = ctx.message.text;
91
 
92
- // Check if it's the allowed chat
93
  if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) {
94
  await ctx.reply("Sorry, I'm only available in the designated group.");
95
  return;
96
  }
97
 
98
- // Show typing indicator
99
  await ctx.replyWithChatAction("typing");
100
 
101
  // Get or create session
102
- let sessionId = sessions.get(chatId);
103
- if (!sessionId) {
104
- sessionId = `telegram_${chatId}`;
105
- sessions.set(chatId, sessionId);
106
  }
 
107
 
108
- // Call OpenCode
109
- const response = await callOpenCode(message, sessionId);
110
 
111
- // Send response
112
  try {
113
- // Telegram has a 4096 character limit
 
 
 
 
 
 
 
 
 
 
 
114
  if (response.length > 4000) {
115
- // Split into multiple messages
116
- const chunks = response.match(/.{1,4000}/gs);
117
  for (const chunk of chunks) {
118
  await ctx.reply(chunk);
119
  }
@@ -121,20 +200,21 @@ bot.on("message:text", async (ctx) => {
121
  await ctx.reply(response);
122
  }
123
  } catch (error) {
124
- console.error("[ERROR] Failed to send message:", error.message);
125
- await ctx.reply("Failed to send response. Please try again.");
126
  }
127
  });
128
 
129
  // Error handler
130
  bot.catch((err) => {
131
- console.error("[ERROR] Bot error:", err);
132
  });
133
 
134
- // Start bot
135
  console.log("[OK] Starting Telegram bot...");
136
  bot.start({
137
  onStart: () => {
138
  console.log("[OK] Telegram bot is running!");
 
139
  },
140
  });
 
1
  const { Bot } = require("grammy");
2
 
3
+ // Environment variables
4
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
5
  const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
6
+ const ZEN_API_KEY = process.env.OPENCODE_ZEN_KEY;
7
 
8
  if (!BOT_TOKEN) {
9
  console.error("[ERROR] TELEGRAM_BOT_TOKEN not set");
10
  process.exit(1);
11
  }
12
+ if (!ZEN_API_KEY) {
13
+ console.error("[ERROR] OPENCODE_ZEN_KEY not set");
14
+ process.exit(1);
15
+ }
16
 
17
+ const ZEN_BASE = "https://opencode.ai/zen/v1";
18
  const bot = new Bot(BOT_TOKEN);
19
 
20
+ // User sessions: chatId -> { model, history }
21
+ const userSessions = new Map();
22
 
23
+ function getEndpoint(modelId) {
24
+ const openaiPrefixes = ["gpt-", "deepseek-", "grok-", "minimax-", "glm-", "kimi-", "big-pickle", "mimo-", "laguna-", "ling-", "north-", "nemotron-"];
25
+ if (openaiPrefixes.some((p) => modelId.startsWith(p) || modelId.includes(p))) {
26
+ return `${ZEN_BASE}/chat/completions`;
27
+ }
28
+ if (modelId.startsWith("claude-") || modelId.startsWith("qwen3")) {
29
+ return `${ZEN_BASE}/messages`;
30
+ }
31
+ return `${ZEN_BASE}/chat/completions`;
32
+ }
 
 
 
33
 
34
+ async function callZenAPI(messages, modelId) {
35
+ const endpoint = getEndpoint(modelId);
36
+ const headers = {
37
+ Authorization: `Bearer ${ZEN_API_KEY}`,
38
+ "Content-Type": "application/json",
39
+ };
40
+
41
+ let body;
42
+ if (endpoint.includes("/messages")) {
43
+ body = { model: modelId, messages, max_tokens: 4096 };
44
+ } else {
45
+ body = { model: modelId, messages, max_tokens: 4096 };
46
+ }
47
 
48
+ const response = await fetch(endpoint, {
49
+ method: "POST",
50
+ headers,
51
+ body: JSON.stringify(body),
52
+ });
 
53
 
54
+ if (!response.ok) {
55
+ const errText = await response.text();
56
+ throw new Error(`API ${response.status}: ${errText.substring(0, 300)}`);
57
+ }
58
 
59
+ const data = await response.json();
60
+
61
+ if (data.content) {
62
+ return data.content.map((c) => c.text || "").join("");
63
+ }
64
+ if (data.choices && data.choices[0]) {
65
+ return data.choices[0].message.content;
66
  }
67
+ return JSON.stringify(data).substring(0, 2000);
68
  }
69
 
70
+ // /start
71
  bot.command("start", async (ctx) => {
72
+ const chatId = ctx.chat.id;
73
+ if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) return;
74
+
75
+ userSessions.set(chatId, { model: "deepseek-v4-flash-free", history: [] });
76
  await ctx.reply(
77
+ "πŸ€– Welcome to OpenCode AI Agent!\n\n" +
78
+ "I'm connected to OpenCode Zen with 40+ AI models.\n\n" +
79
  "Commands:\n" +
80
+ "/model <name> - Switch model\n" +
81
+ "/models - List available models\n" +
82
+ "/new - New conversation\n" +
83
+ "/help - Show help\n\n" +
84
+ "Just send me a message to start chatting!\n" +
85
+ "Default model: DeepSeek V4 Flash (FREE)"
86
  );
87
  });
88
 
89
+ // /help
90
  bot.command("help", async (ctx) => {
91
  await ctx.reply(
92
+ "πŸ“– Help\n\n" +
93
+ "Send any message and I'll respond using AI.\n\n" +
94
+ "Commands:\n" +
95
+ "/start - Initialize bot\n" +
96
+ "/models - List all models\n" +
97
+ "/model <name> - Switch model\n" +
98
+ "/new - Clear history\n" +
99
+ "/help - This message\n\n" +
100
+ "Free models: deepseek-v4-flash-free, mimo-v2.5-free, big-pickle, laguna-s-2.1-free"
101
+ );
102
+ });
103
+
104
+ // /models
105
+ bot.command("models", async (ctx) => {
106
+ await ctx.reply(
107
+ "πŸ“‹ Available Models:\n\n" +
108
+ "πŸ†“ FREE:\n" +
109
+ "β€’ deepseek-v4-flash-free\n" +
110
+ "β€’ mimo-v2.5-free\n" +
111
+ "β€’ big-pickle\n" +
112
+ "β€’ laguna-s-2.1-free\n" +
113
+ "β€’ ling-3.0-flash-free\n" +
114
+ "β€’ north-mini-code-free\n" +
115
+ "β€’ nemotron-3-ultra-free\n\n" +
116
+ "πŸ’° Paid:\n" +
117
+ "β€’ gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5-nano\n" +
118
+ "β€’ claude-sonnet-5, claude-opus-5, claude-haiku-4-5\n" +
119
+ "β€’ gemini-3.6-flash, gemini-3.1-pro\n" +
120
+ "β€’ deepseek-v4-pro, qwen3.7-max, grok-4.5\n\n" +
121
+ "Use: /model <model-id>"
122
  );
123
  });
124
 
125
+ // /model
126
+ bot.command("model", async (ctx) => {
127
+ const chatId = ctx.chat.id;
128
+ if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) return;
129
+
130
+ const args = ctx.message.text.split(" ").slice(1);
131
+ if (args.length === 0) {
132
+ const session = userSessions.get(chatId);
133
+ await ctx.reply(`Current model: ${session?.model || "deepseek-v4-flash-free"}`);
134
+ return;
135
+ }
136
+
137
+ const modelId = args[0].toLowerCase();
138
+ if (!userSessions.has(chatId)) {
139
+ userSessions.set(chatId, { model: modelId, history: [] });
140
+ } else {
141
+ userSessions.get(chatId).model = modelId;
142
+ userSessions.get(chatId).history = [];
143
+ }
144
+
145
+ await ctx.reply(`βœ… Model switched to: ${modelId}\nHistory cleared. Send me a message!`);
146
+ });
147
+
148
+ // /new
149
  bot.command("new", async (ctx) => {
150
  const chatId = ctx.chat.id;
151
+ if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) return;
152
+
153
+ const session = userSessions.get(chatId);
154
+ const model = session?.model || "deepseek-v4-flash-free";
155
+ userSessions.set(chatId, { model, history: [] });
156
+ await ctx.reply("πŸ”„ New conversation started!");
157
  });
158
 
159
  // Handle text messages
 
161
  const chatId = ctx.chat.id;
162
  const message = ctx.message.text;
163
 
164
+ // Check allowed chat
165
  if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) {
166
  await ctx.reply("Sorry, I'm only available in the designated group.");
167
  return;
168
  }
169
 
 
170
  await ctx.replyWithChatAction("typing");
171
 
172
  // Get or create session
173
+ if (!userSessions.has(chatId)) {
174
+ userSessions.set(chatId, { model: "deepseek-v4-flash-free", history: [] });
 
 
175
  }
176
+ const session = userSessions.get(chatId);
177
 
178
+ // Build messages
179
+ const messages = [...session.history, { role: "user", content: message }];
180
 
 
181
  try {
182
+ const response = await callZenAPI(messages, session.model);
183
+
184
+ // Save to history
185
+ session.history.push({ role: "user", content: message });
186
+ session.history.push({ role: "assistant", content: response });
187
+
188
+ // Keep history under 20 messages
189
+ if (session.history.length > 20) {
190
+ session.history = session.history.slice(-20);
191
+ }
192
+
193
+ // Send (split if too long for Telegram's 4096 char limit)
194
  if (response.length > 4000) {
195
+ const chunks = response.match(/.{1,4000}/gs) || [response];
 
196
  for (const chunk of chunks) {
197
  await ctx.reply(chunk);
198
  }
 
200
  await ctx.reply(response);
201
  }
202
  } catch (error) {
203
+ console.error("[ERROR]", error.message);
204
+ await ctx.reply(`❌ Error: ${error.message}`);
205
  }
206
  });
207
 
208
  // Error handler
209
  bot.catch((err) => {
210
+ console.error("[BOT ERROR]", err);
211
  });
212
 
213
+ // Start
214
  console.log("[OK] Starting Telegram bot...");
215
  bot.start({
216
  onStart: () => {
217
  console.log("[OK] Telegram bot is running!");
218
+ console.log("[OK] Chat ID filter:", CHAT_ID || "none (accepting all)");
219
  },
220
  });