| const { Bot } = require("grammy"); |
|
|
| const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; |
| const CHAT_ID = process.env.TELEGRAM_CHAT_ID; |
| const ZEN_API_KEY = process.env.OPENCODE_ZEN_KEY; |
| const ROUTER_URL = process.env.ROUTER_URL || "http://127.0.0.1:7861"; |
|
|
| if (!BOT_TOKEN) { console.error("[ERROR] TELEGRAM_BOT_TOKEN not set"); process.exit(1); } |
| if (!ZEN_API_KEY) { console.error("[ERROR] OPENCODE_ZEN_KEY not set"); process.exit(1); } |
|
|
| console.log("[CONFIG] CHAT_ID filter:", CHAT_ID || "ALL"); |
| console.log("[CONFIG] Zen Key:", ZEN_API_KEY.substring(0, 12) + "..."); |
| console.log("[CONFIG] Router:", ROUTER_URL); |
|
|
| const ZEN_BASE = "https://opencode.ai/zen/v1"; |
| const LOCAL_MODEL = "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF"; |
| const LOCAL_API = "https://abbasyk60-ai-telegram-bot.hf.space/v1/chat/completions"; |
| const FREE_MODELS = ["deepseek-v4-flash-free", "mimo-v2.5-free", "big-pickle", "laguna-s-2.1-free", "ling-3.0-flash-free", "north-mini-code-free", "nemotron-3-ultra-free"]; |
| const bot = new Bot(BOT_TOKEN); |
| const userSessions = new Map(); |
|
|
| async function callAPI(messages, modelId, url, headers, bodyExtra = {}) { |
| const body = { model: modelId, messages, max_tokens: 2048, ...bodyExtra }; |
| const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) }); |
| if (!response.ok) { |
| const err = await response.text(); |
| throw new Error(`API ${response.status}: ${err.substring(0, 200)}`); |
| } |
| const data = await response.json(); |
| if (data.choices?.[0]?.message?.content?.trim()) return data.choices[0].message.content; |
| if (data.choices?.[0]?.message?.reasoning_content?.trim()) return data.choices[0].message.reasoning_content; |
| throw new Error("Empty response"); |
| } |
|
|
| async function callWithFallback(messages, preferredModel) { |
| |
| if (preferredModel === LOCAL_MODEL || preferredModel === "deepseek-r1-7b") { |
| try { |
| const response = await callAPI(messages, LOCAL_MODEL, LOCAL_API, { "Content-Type": "application/json" }); |
| console.log("[OK] Local model responded"); |
| return response; |
| } catch (e) { |
| console.log("[SKIP] Local failed:", e.message.substring(0, 80)); |
| } |
| } |
|
|
| |
| const modelsToTry = preferredModel === LOCAL_MODEL ? FREE_MODELS : [preferredModel, ...FREE_MODELS.filter((m) => m !== preferredModel)]; |
|
|
| for (const model of modelsToTry) { |
| |
| try { |
| const response = await callAPI(messages, model, `${ZEN_BASE}/chat/completions`, { |
| Authorization: `Bearer ${ZEN_API_KEY}`, |
| "Content-Type": "application/json", |
| }); |
| console.log("[OK] Zen responded with:", model); |
| return response; |
| } catch (e) { |
| console.log("[SKIP] Zen failed:", model, e.message.substring(0, 80)); |
| } |
|
|
| |
| try { |
| const response = await callAPI(messages, LOCAL_MODEL, LOCAL_API, { "Content-Type": "application/json" }); |
| console.log("[OK] Local model responded (fallback)"); |
| return response; |
| } catch (e) { |
| console.log("[SKIP] Local fallback failed:", e.message.substring(0, 80)); |
| } |
| } |
|
|
| |
| try { |
| const response = await callAPI(messages, LOCAL_MODEL, LOCAL_API, { "Content-Type": "application/json" }); |
| console.log("[OK] Local model responded (last resort)"); |
| return response; |
| } catch (e) { |
| console.log("[SKIP] Local last resort failed:", e.message.substring(0, 80)); |
| } |
|
|
| throw new Error("All models failed. Try again in a minute."); |
| } |
|
|
| bot.command("start", async (ctx) => { |
| const chatId = ctx.chat.id; |
| if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) return; |
| userSessions.set(chatId, { model: "deepseek-v4-flash-free", history: [] }); |
| await ctx.reply( |
| "Welcome to OpenCode AI Agent!\n\n" + |
| "Commands:\n" + |
| "/model <name> - Switch model\n" + |
| "/models - List available models\n" + |
| "/new - New conversation\n" + |
| "/help - Show help\n\n" + |
| "Default model: DeepSeek V4 Flash (FREE)\n" + |
| "Local model: DeepSeek R1 7B (free, runs on your server)" |
| ); |
| }); |
|
|
| bot.command("help", async (ctx) => { |
| await ctx.reply( |
| "Send any message and I'll respond using AI.\n\n" + |
| "Commands:\n" + |
| "/start - Initialize bot\n" + |
| "/models - List models\n" + |
| "/model <name> - Switch model\n" + |
| "/new - Clear history" |
| ); |
| }); |
|
|
| bot.command("models", async (ctx) => { |
| await ctx.reply( |
| "FREE Models (Zen API):\n" + |
| "- deepseek-v4-flash-free\n" + |
| "- mimo-v2.5-free\n" + |
| "- big-pickle\n" + |
| "- laguna-s-2.1-free\n" + |
| "- ling-3.0-flash-free\n" + |
| "- north-mini-code-free\n" + |
| "- nemotron-3-ultra-free\n\n" + |
| "LOCAL Model (Your Server):\n" + |
| "- deepseek-r1-7b (DeepSeek R1 7B)\n\n" + |
| "Paid: gpt-5.5, gpt-5.4, claude-sonnet-5, gemini-3.6-flash, etc.\n\n" + |
| "/model <model-id>" |
| ); |
| }); |
|
|
| bot.command("model", async (ctx) => { |
| const chatId = ctx.chat.id; |
| if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) return; |
| const args = ctx.message.text.split(" ").slice(1); |
| if (args.length === 0) { |
| const session = userSessions.get(chatId); |
| await ctx.reply("Current: " + (session?.model || "deepseek-v4-flash-free")); |
| return; |
| } |
| let modelId = args[0].toLowerCase(); |
| if (modelId === "deepseek-r1-7b" || modelId === "local") modelId = LOCAL_MODEL; |
| const session = userSessions.get(chatId) || { history: [] }; |
| userSessions.set(chatId, { model: modelId, history: [] }); |
| await ctx.reply("Switched to: " + modelId); |
| }); |
|
|
| bot.command("new", async (ctx) => { |
| const chatId = ctx.chat.id; |
| if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) return; |
| const session = userSessions.get(chatId); |
| userSessions.set(chatId, { model: session?.model || "deepseek-v4-flash-free", history: [] }); |
| await ctx.reply("New conversation started!"); |
| }); |
|
|
| bot.on("message:text", async (ctx) => { |
| const chatId = ctx.chat.id; |
| const message = ctx.message.text; |
|
|
| if (CHAT_ID && chatId.toString() !== CHAT_ID.toString()) { |
| console.log("[BLOCKED] Chat", chatId); |
| return; |
| } |
|
|
| console.log("[MSG]", chatId, ":", message.substring(0, 50)); |
| await ctx.replyWithChatAction("typing"); |
|
|
| if (!userSessions.has(chatId)) { |
| userSessions.set(chatId, { model: "deepseek-v4-flash-free", history: [] }); |
| } |
| const session = userSessions.get(chatId); |
| const messages = [...session.history, { role: "user", content: message }]; |
|
|
| try { |
| const response = await callWithFallback(messages, session.model); |
|
|
| session.history.push({ role: "user", content: message }); |
| session.history.push({ role: "assistant", content: response }); |
| if (session.history.length > 20) session.history = session.history.slice(-20); |
|
|
| if (response.length > 4000) { |
| const chunks = response.match(/.{1,4000}/gs) || [response]; |
| for (const chunk of chunks) await ctx.reply(chunk); |
| } else { |
| await ctx.reply(response); |
| } |
| console.log("[REPLY]", response.length, "chars"); |
| } catch (error) { |
| console.error("[ERROR]", error.message); |
| await ctx.reply("Error: " + error.message); |
| } |
| }); |
|
|
| bot.catch((err) => console.error("[BOT ERROR]", err)); |
|
|
| console.log("[OK] Starting Telegram bot..."); |
| bot.start({ |
| onStart: () => { |
| console.log("[OK] Telegram bot running!"); |
| console.log("[OK] Chat ID:", CHAT_ID || "none"); |
| }, |
| drop_pending_updates: true, |
| }); |
|
|