opencode-telegram-bot 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -18
- package/dist/app.d.ts +5 -0
- package/dist/index.js +211 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,21 +99,20 @@ These are handled directly by the Telegram bot:
|
|
|
99
99
|
|---|---|
|
|
100
100
|
| `/start` | Welcome message |
|
|
101
101
|
| `/new` | Start a new conversation |
|
|
102
|
-
| `/sessions` | List
|
|
103
|
-
| `/switch <id>` | Switch to a different session (prefix match supported) |
|
|
102
|
+
| `/sessions` | List sessions with inline buttons |
|
|
104
103
|
| `/title <text>` | Rename the current session |
|
|
105
|
-
| `/delete <id>` | Delete a session from the server |
|
|
106
104
|
| `/export` | Export the current session as a markdown file |
|
|
107
105
|
| `/export full` | Export with all details (thinking, costs, steps) |
|
|
108
106
|
| `/verbose` | Toggle verbose mode (show thinking and tool calls in chat) |
|
|
109
107
|
| `/verbose on\|off` | Explicitly enable/disable verbose mode |
|
|
110
108
|
| `/model` | Show current model and usage hints |
|
|
111
109
|
| `/model <keyword>` | Search models by keyword |
|
|
112
|
-
| `/model <number>` | Switch to a model from the last search |
|
|
113
110
|
| `/model default` | Reset to the default model |
|
|
114
111
|
| `/usage` | Show token and cost usage for this session |
|
|
115
112
|
| `/help` | Show available commands |
|
|
116
113
|
|
|
114
|
+
The bot also registers these commands in Telegram's command menu (the `/` button), plus any OpenCode server commands discovered at startup (excluding hidden ones like `/init` and `/review`).
|
|
115
|
+
|
|
117
116
|
### Verbose Mode
|
|
118
117
|
|
|
119
118
|
By default, the bot only shows the assistant's final text response. Use `/verbose` to toggle verbose mode (or `/verbose on|off` to set it explicitly), which also displays:
|
|
@@ -141,13 +140,11 @@ Use `/model` to search and switch models without typing long names:
|
|
|
141
140
|
```
|
|
142
141
|
You: /model sonnet
|
|
143
142
|
Bot: Models matching "sonnet":
|
|
144
|
-
|
|
145
|
-
2. claude-sonnet-4 (anthropic)
|
|
146
|
-
|
|
147
|
-
Use /model <number> to select.
|
|
143
|
+
Tap a model to select.
|
|
148
144
|
|
|
149
|
-
You:
|
|
145
|
+
You: [tap "claude-sonnet-4-5 (google-vertex-anthropic)"]
|
|
150
146
|
Bot: Switched to claude-sonnet-4-5 (google-vertex-anthropic)
|
|
147
|
+
|
|
151
148
|
```
|
|
152
149
|
|
|
153
150
|
Other commands:
|
|
@@ -219,21 +216,17 @@ Bot: [agent response about tests - new session auto-titled "Fix the broken test
|
|
|
219
216
|
|
|
220
217
|
You: /sessions
|
|
221
218
|
Bot: Your sessions:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Use /switch <id> to switch sessions.
|
|
225
|
-
|
|
226
|
-
You: /switch def6
|
|
227
|
-
Bot: Switched to session: Auth refactoring (def67890)
|
|
219
|
+
Current session: Fix the broken tests
|
|
220
|
+
Tap a session to switch or delete.
|
|
228
221
|
|
|
229
222
|
You: What were we working on?
|
|
230
223
|
Bot: [agent responds with context from the auth refactoring session]
|
|
231
224
|
|
|
232
|
-
You:
|
|
233
|
-
Bot:
|
|
225
|
+
You: [tap "Auth refactoring"]
|
|
226
|
+
Bot: Switched to session: Auth refactoring
|
|
234
227
|
```
|
|
235
228
|
|
|
236
|
-
The `/sessions` list only shows sessions created by the Telegram bot, not sessions from other OpenCode clients (like the TUI). You cannot delete the currently active session -- use `/new`
|
|
229
|
+
The `/sessions` list only shows sessions created by the Telegram bot, not sessions from other OpenCode clients (like the TUI). You cannot delete the currently active session -- use `/new` first.
|
|
237
230
|
|
|
238
231
|
## Session Persistence
|
|
239
232
|
|
package/dist/app.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ interface TelegramBot {
|
|
|
36
36
|
help: (fn: (ctx: any) => void | Promise<void>) => void;
|
|
37
37
|
command: (command: string, fn: (ctx: any) => void | Promise<void>) => void;
|
|
38
38
|
on: (event: string, fn: (ctx: any) => void | Promise<void>) => void;
|
|
39
|
+
action: (trigger: string | RegExp, fn: (ctx: any) => void | Promise<void>) => void;
|
|
39
40
|
launch: () => Promise<void>;
|
|
40
41
|
stop: (reason?: string) => void;
|
|
41
42
|
telegram: {
|
|
@@ -48,6 +49,10 @@ interface TelegramBot {
|
|
|
48
49
|
getFileLink: (fileId: string) => Promise<{
|
|
49
50
|
toString(): string;
|
|
50
51
|
}>;
|
|
52
|
+
setMyCommands: (commands: Array<{
|
|
53
|
+
command: string;
|
|
54
|
+
description: string;
|
|
55
|
+
}>) => Promise<unknown>;
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
58
|
export declare function startTelegram(options: StartOptions): Promise<TelegramBot>;
|
package/dist/index.js
CHANGED
|
@@ -18589,6 +18589,9 @@ async function startTelegram(options) {
|
|
|
18589
18589
|
const client = options.client || client_createOpencodeClient({ baseUrl: url });
|
|
18590
18590
|
// Verify connection to the OpenCode server and fetch available commands
|
|
18591
18591
|
const opencodeCommands = new Set();
|
|
18592
|
+
const opencodeCommandMenu = [];
|
|
18593
|
+
const hiddenOpenCodeCommands = new Set(["init", "review", "reviews"]);
|
|
18594
|
+
const isHiddenOpenCodeCommand = (name) => hiddenOpenCodeCommands.has(name.toLowerCase());
|
|
18592
18595
|
let projectDirectory = "";
|
|
18593
18596
|
try {
|
|
18594
18597
|
const sessions = await client.session.list();
|
|
@@ -18606,7 +18609,16 @@ async function startTelegram(options) {
|
|
|
18606
18609
|
const cmds = await client.command.list();
|
|
18607
18610
|
if (cmds.data) {
|
|
18608
18611
|
for (const cmd of cmds.data) {
|
|
18609
|
-
|
|
18612
|
+
const name = cmd.name;
|
|
18613
|
+
if (!name)
|
|
18614
|
+
continue;
|
|
18615
|
+
opencodeCommands.add(name);
|
|
18616
|
+
if (!isHiddenOpenCodeCommand(name)) {
|
|
18617
|
+
opencodeCommandMenu.push({
|
|
18618
|
+
command: name,
|
|
18619
|
+
description: cmd.description || "OpenCode command",
|
|
18620
|
+
});
|
|
18621
|
+
}
|
|
18610
18622
|
}
|
|
18611
18623
|
console.log(`[Telegram] Available OpenCode commands: ${[...opencodeCommands].join(", ")}`);
|
|
18612
18624
|
}
|
|
@@ -18618,6 +18630,17 @@ async function startTelegram(options) {
|
|
|
18618
18630
|
const telegramCommands = new Set([
|
|
18619
18631
|
"start", "help", "new", "sessions", "switch", "title", "delete", "export", "verbose", "model", "usage",
|
|
18620
18632
|
]);
|
|
18633
|
+
const telegramCommandMenu = [
|
|
18634
|
+
{ command: "new", description: "Start a new conversation" },
|
|
18635
|
+
{ command: "sessions", description: "List your sessions" },
|
|
18636
|
+
{ command: "title", description: "Rename a session (/title <text>)" },
|
|
18637
|
+
{ command: "export", description: "Export session (/export full for details)" },
|
|
18638
|
+
{ command: "verbose", description: "Toggle verbose mode" },
|
|
18639
|
+
{ command: "model", description: "Search models (/model <keyword>)" },
|
|
18640
|
+
{ command: "usage", description: "Show token and cost usage" },
|
|
18641
|
+
{ command: "help", description: "Show available commands" },
|
|
18642
|
+
];
|
|
18643
|
+
const getVisibleOpenCodeCommands = () => [...opencodeCommands].filter((command) => !isHiddenOpenCodeCommand(command));
|
|
18621
18644
|
// Map of chatId -> sessionId for the active session per chat
|
|
18622
18645
|
const chatSessions = new Map();
|
|
18623
18646
|
// Set of all session IDs ever created/used by this bot (for filtering)
|
|
@@ -18801,6 +18824,25 @@ async function startTelegram(options) {
|
|
|
18801
18824
|
}
|
|
18802
18825
|
return promptBody;
|
|
18803
18826
|
}
|
|
18827
|
+
function getChatIdFromContext(ctx) {
|
|
18828
|
+
const direct = ctx.chat?.id;
|
|
18829
|
+
if (direct)
|
|
18830
|
+
return direct.toString();
|
|
18831
|
+
const fromCallback = ctx.callbackQuery?.message?.chat?.id;
|
|
18832
|
+
if (fromCallback)
|
|
18833
|
+
return fromCallback.toString();
|
|
18834
|
+
return null;
|
|
18835
|
+
}
|
|
18836
|
+
async function answerAndEdit(ctx, text) {
|
|
18837
|
+
if (typeof ctx.answerCbQuery === "function") {
|
|
18838
|
+
await ctx.answerCbQuery();
|
|
18839
|
+
}
|
|
18840
|
+
if (typeof ctx.editMessageText === "function") {
|
|
18841
|
+
await ctx.editMessageText(text);
|
|
18842
|
+
return;
|
|
18843
|
+
}
|
|
18844
|
+
await ctx.reply(text);
|
|
18845
|
+
}
|
|
18804
18846
|
async function getTelegramFileUrl(fileId) {
|
|
18805
18847
|
const link = await bot.telegram.getFileLink(fileId);
|
|
18806
18848
|
return link.toString();
|
|
@@ -19100,6 +19142,25 @@ async function startTelegram(options) {
|
|
|
19100
19142
|
const bot = options.botFactory
|
|
19101
19143
|
? options.botFactory(token)
|
|
19102
19144
|
: new lib.Telegraf(token);
|
|
19145
|
+
async function registerCommandMenu() {
|
|
19146
|
+
const combined = [...telegramCommandMenu, ...opencodeCommandMenu];
|
|
19147
|
+
const seen = new Set();
|
|
19148
|
+
const commands = [];
|
|
19149
|
+
for (const entry of combined) {
|
|
19150
|
+
if (!entry.command || seen.has(entry.command))
|
|
19151
|
+
continue;
|
|
19152
|
+
seen.add(entry.command);
|
|
19153
|
+
commands.push(entry);
|
|
19154
|
+
}
|
|
19155
|
+
if (commands.length === 0)
|
|
19156
|
+
return;
|
|
19157
|
+
try {
|
|
19158
|
+
await bot.telegram.setMyCommands(commands);
|
|
19159
|
+
}
|
|
19160
|
+
catch (err) {
|
|
19161
|
+
console.warn("[Telegram] Failed to register command menu:", err);
|
|
19162
|
+
}
|
|
19163
|
+
}
|
|
19103
19164
|
// Middleware to check if the user is authorized
|
|
19104
19165
|
bot.use((ctx, next) => {
|
|
19105
19166
|
if (!authorizedUserId) {
|
|
@@ -19115,26 +19176,26 @@ async function startTelegram(options) {
|
|
|
19115
19176
|
return;
|
|
19116
19177
|
}
|
|
19117
19178
|
});
|
|
19179
|
+
await registerCommandMenu();
|
|
19118
19180
|
// Handle /start command
|
|
19119
19181
|
bot.start((ctx) => {
|
|
19120
19182
|
let msg = "Hello! I'm your OpenCode bot. Send me a message and I'll forward it to the OpenCode agent.\n\n" +
|
|
19121
19183
|
"Bot commands:\n" +
|
|
19122
19184
|
"/new - Start a new conversation\n" +
|
|
19123
|
-
"/sessions - List your sessions\n" +
|
|
19124
|
-
"/switch <number> - Switch to a different session\n" +
|
|
19185
|
+
"/sessions - List your sessions (buttons)\n" +
|
|
19125
19186
|
"/title <text> - Rename the current session\n" +
|
|
19126
|
-
"/delete <number> - Delete a session\n" +
|
|
19127
19187
|
"/export - Export the current session as a markdown file\n" +
|
|
19128
19188
|
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
19129
19189
|
"/verbose - Toggle verbose mode (show thinking and tool calls)\n" +
|
|
19130
19190
|
"/verbose on|off - Set verbose mode explicitly\n" +
|
|
19131
|
-
"/model -
|
|
19191
|
+
"/model <keyword> - Search available models\n" +
|
|
19132
19192
|
"/usage - Show token and cost usage for this session\n" +
|
|
19133
19193
|
"/help - Show this help message\n";
|
|
19134
|
-
|
|
19194
|
+
const visibleOpenCodeCommands = getVisibleOpenCodeCommands();
|
|
19195
|
+
if (visibleOpenCodeCommands.length > 0) {
|
|
19135
19196
|
msg +=
|
|
19136
19197
|
"\nOpenCode commands are also available:\n" +
|
|
19137
|
-
|
|
19198
|
+
visibleOpenCodeCommands.map((c) => `/${c}`).join(", ");
|
|
19138
19199
|
}
|
|
19139
19200
|
ctx.reply(msg);
|
|
19140
19201
|
});
|
|
@@ -19143,21 +19204,20 @@ async function startTelegram(options) {
|
|
|
19143
19204
|
let msg = "Send me any message and I'll process it using OpenCode.\n\n" +
|
|
19144
19205
|
"Bot commands:\n" +
|
|
19145
19206
|
"/new - Start a new conversation\n" +
|
|
19146
|
-
"/sessions - List your sessions\n" +
|
|
19147
|
-
"/switch <number> - Switch to a different session\n" +
|
|
19207
|
+
"/sessions - List your sessions (buttons)\n" +
|
|
19148
19208
|
"/title <text> - Rename the current session\n" +
|
|
19149
|
-
"/delete <number> - Delete a session\n" +
|
|
19150
19209
|
"/export - Export the current session as a markdown file\n" +
|
|
19151
19210
|
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
19152
19211
|
"/verbose - Toggle verbose mode (show thinking and tool calls)\n" +
|
|
19153
19212
|
"/verbose on|off - Set verbose mode explicitly\n" +
|
|
19154
|
-
"/model -
|
|
19213
|
+
"/model <keyword> - Search available models\n" +
|
|
19155
19214
|
"/usage - Show token and cost usage for this session\n" +
|
|
19156
19215
|
"/help - Show this help message\n";
|
|
19157
|
-
|
|
19216
|
+
const visibleOpenCodeCommands = getVisibleOpenCodeCommands();
|
|
19217
|
+
if (visibleOpenCodeCommands.length > 0) {
|
|
19158
19218
|
msg +=
|
|
19159
19219
|
"\nOpenCode commands:\n" +
|
|
19160
|
-
|
|
19220
|
+
visibleOpenCodeCommands.map((c) => `/${c}`).join(", ");
|
|
19161
19221
|
}
|
|
19162
19222
|
ctx.reply(msg);
|
|
19163
19223
|
});
|
|
@@ -19182,16 +19242,9 @@ async function startTelegram(options) {
|
|
|
19182
19242
|
.sort((a, b) => b.time.updated - a.time.updated);
|
|
19183
19243
|
}
|
|
19184
19244
|
/**
|
|
19185
|
-
* Resolve a user argument to a session
|
|
19186
|
-
* (1-based, as shown by /sessions) or a session ID / prefix.
|
|
19245
|
+
* Resolve a user argument to a session ID / prefix.
|
|
19187
19246
|
*/
|
|
19188
19247
|
function resolveSession(sessions, arg) {
|
|
19189
|
-
// Try numeric index first
|
|
19190
|
-
const num = parseInt(arg, 10);
|
|
19191
|
-
if (!isNaN(num) && String(num) === arg && num >= 1 && num <= sessions.length) {
|
|
19192
|
-
return sessions[num - 1];
|
|
19193
|
-
}
|
|
19194
|
-
// Fall back to ID / prefix match
|
|
19195
19248
|
return sessions.find((s) => s.id === arg || s.id.startsWith(arg));
|
|
19196
19249
|
}
|
|
19197
19250
|
// Handle /sessions command - list Telegram bot sessions only
|
|
@@ -19204,28 +19257,48 @@ async function startTelegram(options) {
|
|
|
19204
19257
|
await ctx.reply("No sessions found.");
|
|
19205
19258
|
return;
|
|
19206
19259
|
}
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19260
|
+
const activeSession = sessions.find((session) => session.id === activeSessionId);
|
|
19261
|
+
const msgLines = ["Your sessions:"];
|
|
19262
|
+
if (activeSession) {
|
|
19263
|
+
msgLines.push(`Current session: ${activeSession.title}`);
|
|
19264
|
+
}
|
|
19265
|
+
const otherSessions = sessions.filter((session) => session.id !== activeSessionId);
|
|
19266
|
+
if (otherSessions.length === 0) {
|
|
19267
|
+
msgLines.push("This is your only session.");
|
|
19268
|
+
await ctx.reply(msgLines.join("\n"));
|
|
19269
|
+
return;
|
|
19270
|
+
}
|
|
19271
|
+
msgLines.push("Tap a session to switch or delete.");
|
|
19272
|
+
const keyboard = [];
|
|
19273
|
+
otherSessions.forEach((session) => {
|
|
19274
|
+
keyboard.push([
|
|
19275
|
+
{
|
|
19276
|
+
text: session.title,
|
|
19277
|
+
callback_data: `switch:${session.id}`,
|
|
19278
|
+
},
|
|
19279
|
+
{
|
|
19280
|
+
text: "Delete",
|
|
19281
|
+
callback_data: `delete:${session.id}`,
|
|
19282
|
+
},
|
|
19283
|
+
]);
|
|
19284
|
+
});
|
|
19285
|
+
await ctx.reply(msgLines.join("\n"), {
|
|
19286
|
+
reply_markup: {
|
|
19287
|
+
inline_keyboard: keyboard,
|
|
19288
|
+
},
|
|
19213
19289
|
});
|
|
19214
|
-
msg += `\nUse /switch <number> to switch sessions.`;
|
|
19215
|
-
msg += `\nUse /delete <number> to delete a session.`;
|
|
19216
|
-
await ctx.reply(msg);
|
|
19217
19290
|
}
|
|
19218
19291
|
catch (err) {
|
|
19219
19292
|
console.error("[Telegram] Error listing sessions:", err);
|
|
19220
19293
|
await ctx.reply("Failed to list sessions.");
|
|
19221
19294
|
}
|
|
19222
19295
|
});
|
|
19223
|
-
// Handle /switch <
|
|
19296
|
+
// Handle /switch <id> command - switch to a different session
|
|
19224
19297
|
bot.command("switch", async (ctx) => {
|
|
19225
19298
|
const chatId = ctx.chat.id.toString();
|
|
19226
19299
|
const args = ctx.message.text.replace(/^\/switch\s*/, "").trim();
|
|
19227
19300
|
if (!args) {
|
|
19228
|
-
await ctx.reply("Usage: /switch <
|
|
19301
|
+
await ctx.reply("Usage: /switch <session id>\n\nUse /sessions to see available sessions.");
|
|
19229
19302
|
return;
|
|
19230
19303
|
}
|
|
19231
19304
|
try {
|
|
@@ -19274,12 +19347,12 @@ async function startTelegram(options) {
|
|
|
19274
19347
|
await ctx.reply("Failed to rename session.");
|
|
19275
19348
|
}
|
|
19276
19349
|
});
|
|
19277
|
-
// Handle /delete <
|
|
19350
|
+
// Handle /delete <id> command - delete a session
|
|
19278
19351
|
bot.command("delete", async (ctx) => {
|
|
19279
19352
|
const chatId = ctx.chat.id.toString();
|
|
19280
19353
|
const args = ctx.message.text.replace(/^\/delete\s*/, "").trim();
|
|
19281
19354
|
if (!args) {
|
|
19282
|
-
await ctx.reply("Usage: /delete <
|
|
19355
|
+
await ctx.reply("Usage: /delete <session id>\n\nUse /sessions to see available sessions.");
|
|
19283
19356
|
return;
|
|
19284
19357
|
}
|
|
19285
19358
|
try {
|
|
@@ -19313,6 +19386,61 @@ async function startTelegram(options) {
|
|
|
19313
19386
|
await ctx.reply("Failed to delete session.");
|
|
19314
19387
|
}
|
|
19315
19388
|
});
|
|
19389
|
+
bot.action(/^switch:(.+)$/, async (ctx) => {
|
|
19390
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19391
|
+
const sessionId = ctx.match?.[1];
|
|
19392
|
+
if (!chatId || !sessionId)
|
|
19393
|
+
return;
|
|
19394
|
+
try {
|
|
19395
|
+
const sessions = await getKnownSessions();
|
|
19396
|
+
const match = sessions.find((session) => session.id === sessionId);
|
|
19397
|
+
if (!match) {
|
|
19398
|
+
await answerAndEdit(ctx, "Session not found. Use /sessions to refresh.");
|
|
19399
|
+
return;
|
|
19400
|
+
}
|
|
19401
|
+
chatSessions.set(chatId, match.id);
|
|
19402
|
+
saveSessions();
|
|
19403
|
+
console.log(`[Telegram] Switched chat ${chatId} to session ${match.id}`);
|
|
19404
|
+
await answerAndEdit(ctx, `Switched to session: ${match.title}`);
|
|
19405
|
+
}
|
|
19406
|
+
catch (err) {
|
|
19407
|
+
console.error("[Telegram] Error switching session:", err);
|
|
19408
|
+
await answerAndEdit(ctx, "Failed to switch session.");
|
|
19409
|
+
}
|
|
19410
|
+
});
|
|
19411
|
+
bot.action(/^delete:(.+)$/, async (ctx) => {
|
|
19412
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19413
|
+
const sessionId = ctx.match?.[1];
|
|
19414
|
+
if (!chatId || !sessionId)
|
|
19415
|
+
return;
|
|
19416
|
+
try {
|
|
19417
|
+
const sessions = await getKnownSessions();
|
|
19418
|
+
const match = sessions.find((session) => session.id === sessionId);
|
|
19419
|
+
if (!match) {
|
|
19420
|
+
await answerAndEdit(ctx, "Session not found. Use /sessions to refresh.");
|
|
19421
|
+
return;
|
|
19422
|
+
}
|
|
19423
|
+
const activeSessionId = chatSessions.get(chatId);
|
|
19424
|
+
if (match.id === activeSessionId) {
|
|
19425
|
+
await answerAndEdit(ctx, "Cannot delete the active session. Use /new or /switch first, then delete it.");
|
|
19426
|
+
return;
|
|
19427
|
+
}
|
|
19428
|
+
const result = await client.session.delete({
|
|
19429
|
+
path: { id: match.id },
|
|
19430
|
+
});
|
|
19431
|
+
if (result.error) {
|
|
19432
|
+
throw new Error(JSON.stringify(result.error));
|
|
19433
|
+
}
|
|
19434
|
+
knownSessionIds.delete(match.id);
|
|
19435
|
+
saveSessions();
|
|
19436
|
+
console.log(`[Telegram] Deleted session ${match.id}`);
|
|
19437
|
+
await answerAndEdit(ctx, `Deleted session: ${match.title}`);
|
|
19438
|
+
}
|
|
19439
|
+
catch (err) {
|
|
19440
|
+
console.error("[Telegram] Error deleting session:", err);
|
|
19441
|
+
await answerAndEdit(ctx, "Failed to delete session.");
|
|
19442
|
+
}
|
|
19443
|
+
});
|
|
19316
19444
|
// Handle /verbose command - toggle verbose mode for this chat
|
|
19317
19445
|
// Usage: /verbose, /verbose on, /verbose off
|
|
19318
19446
|
bot.command("verbose", (ctx) => {
|
|
@@ -19368,7 +19496,7 @@ async function startTelegram(options) {
|
|
|
19368
19496
|
return results;
|
|
19369
19497
|
}
|
|
19370
19498
|
// Handle /model command
|
|
19371
|
-
// Usage: /model, /model <keyword>, /model
|
|
19499
|
+
// Usage: /model, /model <keyword>, /model default
|
|
19372
19500
|
bot.command("model", async (ctx) => {
|
|
19373
19501
|
const chatId = ctx.chat.id.toString();
|
|
19374
19502
|
const args = ctx.message.text.replace(/^\/model\s*/, "").trim();
|
|
@@ -19385,24 +19513,6 @@ async function startTelegram(options) {
|
|
|
19385
19513
|
await ctx.reply("Model reset to the default model.");
|
|
19386
19514
|
return;
|
|
19387
19515
|
}
|
|
19388
|
-
const asNumber = Number.parseInt(args, 10);
|
|
19389
|
-
if (!Number.isNaN(asNumber) && String(asNumber) === args) {
|
|
19390
|
-
const results = chatModelSearchResults.get(chatId) || [];
|
|
19391
|
-
if (results.length === 0) {
|
|
19392
|
-
await ctx.reply("No recent search results. Use /model <keyword> first.");
|
|
19393
|
-
return;
|
|
19394
|
-
}
|
|
19395
|
-
if (asNumber < 1 || asNumber > results.length) {
|
|
19396
|
-
await ctx.reply("Invalid selection. Use /model <number> from the latest search results.");
|
|
19397
|
-
return;
|
|
19398
|
-
}
|
|
19399
|
-
const selection = results[asNumber - 1];
|
|
19400
|
-
const value = `${selection.providerID}/${selection.modelID}`;
|
|
19401
|
-
chatModelOverride.set(chatId, value);
|
|
19402
|
-
saveSessions();
|
|
19403
|
-
await ctx.reply(`Switched to ${selection.displayName}`);
|
|
19404
|
-
return;
|
|
19405
|
-
}
|
|
19406
19516
|
try {
|
|
19407
19517
|
const results = await searchModels(args);
|
|
19408
19518
|
if (results.length === 0) {
|
|
@@ -19411,21 +19521,60 @@ async function startTelegram(options) {
|
|
|
19411
19521
|
}
|
|
19412
19522
|
const limited = results.slice(0, 10);
|
|
19413
19523
|
chatModelSearchResults.set(chatId, limited);
|
|
19414
|
-
let msg = `Models matching "${args}"
|
|
19415
|
-
for (const [index, item] of limited.entries()) {
|
|
19416
|
-
msg += `${index + 1}. ${item.displayName}\n`;
|
|
19417
|
-
}
|
|
19524
|
+
let msg = `Models matching "${args}":`;
|
|
19418
19525
|
if (results.length > limited.length) {
|
|
19419
19526
|
msg += `\nFound ${results.length} models. Refine your search to narrow the list.`;
|
|
19420
19527
|
}
|
|
19421
|
-
msg += "\
|
|
19422
|
-
|
|
19528
|
+
msg += "\nTap a model to select.";
|
|
19529
|
+
const keyboard = limited.map((item, index) => [
|
|
19530
|
+
{
|
|
19531
|
+
text: item.displayName,
|
|
19532
|
+
callback_data: `model:${index + 1}`,
|
|
19533
|
+
},
|
|
19534
|
+
]);
|
|
19535
|
+
keyboard.push([
|
|
19536
|
+
{ text: "Reset to default", callback_data: "model_default" },
|
|
19537
|
+
]);
|
|
19538
|
+
await ctx.reply(msg, {
|
|
19539
|
+
reply_markup: {
|
|
19540
|
+
inline_keyboard: keyboard,
|
|
19541
|
+
},
|
|
19542
|
+
});
|
|
19423
19543
|
}
|
|
19424
19544
|
catch (err) {
|
|
19425
19545
|
console.error("[Telegram] Error searching models:", err);
|
|
19426
19546
|
await ctx.reply("Failed to list models. Try again later.");
|
|
19427
19547
|
}
|
|
19428
19548
|
});
|
|
19549
|
+
bot.action(/^model:(\d+)$/, async (ctx) => {
|
|
19550
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19551
|
+
const indexText = ctx.match?.[1];
|
|
19552
|
+
if (!chatId || !indexText)
|
|
19553
|
+
return;
|
|
19554
|
+
const selectionIndex = Number.parseInt(indexText, 10);
|
|
19555
|
+
const results = chatModelSearchResults.get(chatId) || [];
|
|
19556
|
+
if (results.length === 0) {
|
|
19557
|
+
await answerAndEdit(ctx, "No recent search results. Use /model <keyword> first.");
|
|
19558
|
+
return;
|
|
19559
|
+
}
|
|
19560
|
+
if (selectionIndex < 1 || selectionIndex > results.length) {
|
|
19561
|
+
await answerAndEdit(ctx, "Invalid selection. Use the latest model search results.");
|
|
19562
|
+
return;
|
|
19563
|
+
}
|
|
19564
|
+
const selection = results[selectionIndex - 1];
|
|
19565
|
+
const value = `${selection.providerID}/${selection.modelID}`;
|
|
19566
|
+
chatModelOverride.set(chatId, value);
|
|
19567
|
+
saveSessions();
|
|
19568
|
+
await answerAndEdit(ctx, `Switched to ${selection.displayName}`);
|
|
19569
|
+
});
|
|
19570
|
+
bot.action("model_default", async (ctx) => {
|
|
19571
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19572
|
+
if (!chatId)
|
|
19573
|
+
return;
|
|
19574
|
+
chatModelOverride.delete(chatId);
|
|
19575
|
+
saveSessions();
|
|
19576
|
+
await answerAndEdit(ctx, "Model reset to the default model.");
|
|
19577
|
+
});
|
|
19429
19578
|
// Handle /usage command - show token and cost usage for current session
|
|
19430
19579
|
bot.command("usage", async (ctx) => {
|
|
19431
19580
|
const chatId = ctx.chat.id.toString();
|
|
@@ -19738,7 +19887,9 @@ async function startTelegram(options) {
|
|
|
19738
19887
|
}
|
|
19739
19888
|
// Check if it's a known OpenCode command
|
|
19740
19889
|
if (!opencodeCommands.has(commandName)) {
|
|
19741
|
-
const available =
|
|
19890
|
+
const available = getVisibleOpenCodeCommands()
|
|
19891
|
+
.map((c) => `/${c}`)
|
|
19892
|
+
.join(", ");
|
|
19742
19893
|
await ctx.reply(`Unknown command: /${commandName}\n\n` +
|
|
19743
19894
|
`Available OpenCode commands: ${available || "none"}\n` +
|
|
19744
19895
|
`Bot commands: /new, /help`);
|
|
@@ -19804,22 +19955,6 @@ async function startTelegram(options) {
|
|
|
19804
19955
|
}
|
|
19805
19956
|
return bot;
|
|
19806
19957
|
}
|
|
19807
|
-
/**
|
|
19808
|
-
* Format a Unix timestamp (seconds) into a human-readable relative time.
|
|
19809
|
-
*/
|
|
19810
|
-
function formatAge(timestamp) {
|
|
19811
|
-
const now = Date.now() / 1000;
|
|
19812
|
-
const diff = now - timestamp;
|
|
19813
|
-
if (diff < 60)
|
|
19814
|
-
return "just now";
|
|
19815
|
-
if (diff < 3600)
|
|
19816
|
-
return `${Math.floor(diff / 60)} min ago`;
|
|
19817
|
-
if (diff < 86400)
|
|
19818
|
-
return `${Math.floor(diff / 3600)} hours ago`;
|
|
19819
|
-
if (diff < 604800)
|
|
19820
|
-
return `${Math.floor(diff / 86400)} days ago`;
|
|
19821
|
-
return `${Math.floor(diff / 604800)} weeks ago`;
|
|
19822
|
-
}
|
|
19823
19958
|
/**
|
|
19824
19959
|
* Truncate text to a maximum length, appending "..." if truncated.
|
|
19825
19960
|
*/
|