File size: 7,391 Bytes
ec55fef 68cd3b2 5fc279e ec55fef 68cd3b2 ec55fef 68cd3b2 ec55fef 9091de1 5fc279e a69619e ec55fef 68cd3b2 5fc279e 68cd3b2 ec55fef 5fc279e ec55fef 5fc279e 68cd3b2 ec55fef 5fc279e 68cd3b2 ec55fef 68cd3b2 ec55fef 5fc279e 68cd3b2 ec55fef 68cd3b2 ec55fef 68cd3b2 5fc279e 68cd3b2 ec55fef 68cd3b2 ec55fef 5fc279e ec55fef 68cd3b2 5fc279e ec55fef 5fc279e ec55fef 5fc279e 68cd3b2 5fc279e ec55fef 68cd3b2 8186dc1 68cd3b2 ec55fef 68cd3b2 ec55fef 68cd3b2 5fc279e 68cd3b2 5fc279e 68cd3b2 ec55fef 5fc279e ec55fef 5fc279e 68cd3b2 5fc279e ec55fef 68cd3b2 ec55fef 68cd3b2 ec55fef 68cd3b2 ec55fef 68cd3b2 ec55fef 68cd3b2 8186dc1 68cd3b2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | 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 user wants local model, try it first
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));
}
}
// Try preferred model on Zen
const modelsToTry = preferredModel === LOCAL_MODEL ? FREE_MODELS : [preferredModel, ...FREE_MODELS.filter((m) => m !== preferredModel)];
for (const model of modelsToTry) {
// Try Zen API
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 local model as fallback
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));
}
}
// Last resort: try local model alone
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,
});
|