opencode-telegram-bot 1.0.5 → 1.0.6
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 +6 -12
- package/dist/app.d.ts +1 -0
- package/dist/index.js +154 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,17 +99,14 @@ 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 |
|
|
@@ -141,13 +138,11 @@ Use `/model` to search and switch models without typing long names:
|
|
|
141
138
|
```
|
|
142
139
|
You: /model sonnet
|
|
143
140
|
Bot: Models matching "sonnet":
|
|
144
|
-
|
|
145
|
-
2. claude-sonnet-4 (anthropic)
|
|
141
|
+
Tap a model to select.
|
|
146
142
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
You: /model 1
|
|
143
|
+
You: [tap "claude-sonnet-4-5 (google-vertex-anthropic)"]
|
|
150
144
|
Bot: Switched to claude-sonnet-4-5 (google-vertex-anthropic)
|
|
145
|
+
|
|
151
146
|
```
|
|
152
147
|
|
|
153
148
|
Other commands:
|
|
@@ -219,9 +214,8 @@ Bot: [agent response about tests - new session auto-titled "Fix the broken test
|
|
|
219
214
|
|
|
220
215
|
You: /sessions
|
|
221
216
|
Bot: Your sessions:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Use /switch <id> to switch sessions.
|
|
217
|
+
Current session: Fix the broken tests
|
|
218
|
+
Tap a session to switch or delete.
|
|
225
219
|
|
|
226
220
|
You: /switch def6
|
|
227
221
|
Bot: Switched to session: Auth refactoring (def67890)
|
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: {
|
package/dist/index.js
CHANGED
|
@@ -18801,6 +18801,25 @@ async function startTelegram(options) {
|
|
|
18801
18801
|
}
|
|
18802
18802
|
return promptBody;
|
|
18803
18803
|
}
|
|
18804
|
+
function getChatIdFromContext(ctx) {
|
|
18805
|
+
const direct = ctx.chat?.id;
|
|
18806
|
+
if (direct)
|
|
18807
|
+
return direct.toString();
|
|
18808
|
+
const fromCallback = ctx.callbackQuery?.message?.chat?.id;
|
|
18809
|
+
if (fromCallback)
|
|
18810
|
+
return fromCallback.toString();
|
|
18811
|
+
return null;
|
|
18812
|
+
}
|
|
18813
|
+
async function answerAndEdit(ctx, text) {
|
|
18814
|
+
if (typeof ctx.answerCbQuery === "function") {
|
|
18815
|
+
await ctx.answerCbQuery();
|
|
18816
|
+
}
|
|
18817
|
+
if (typeof ctx.editMessageText === "function") {
|
|
18818
|
+
await ctx.editMessageText(text);
|
|
18819
|
+
return;
|
|
18820
|
+
}
|
|
18821
|
+
await ctx.reply(text);
|
|
18822
|
+
}
|
|
18804
18823
|
async function getTelegramFileUrl(fileId) {
|
|
18805
18824
|
const link = await bot.telegram.getFileLink(fileId);
|
|
18806
18825
|
return link.toString();
|
|
@@ -19121,9 +19140,7 @@ async function startTelegram(options) {
|
|
|
19121
19140
|
"Bot commands:\n" +
|
|
19122
19141
|
"/new - Start a new conversation\n" +
|
|
19123
19142
|
"/sessions - List your sessions\n" +
|
|
19124
|
-
"/switch <number> - Switch to a different session\n" +
|
|
19125
19143
|
"/title <text> - Rename the current session\n" +
|
|
19126
|
-
"/delete <number> - Delete a session\n" +
|
|
19127
19144
|
"/export - Export the current session as a markdown file\n" +
|
|
19128
19145
|
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
19129
19146
|
"/verbose - Toggle verbose mode (show thinking and tool calls)\n" +
|
|
@@ -19144,9 +19161,7 @@ async function startTelegram(options) {
|
|
|
19144
19161
|
"Bot commands:\n" +
|
|
19145
19162
|
"/new - Start a new conversation\n" +
|
|
19146
19163
|
"/sessions - List your sessions\n" +
|
|
19147
|
-
"/switch <number> - Switch to a different session\n" +
|
|
19148
19164
|
"/title <text> - Rename the current session\n" +
|
|
19149
|
-
"/delete <number> - Delete a session\n" +
|
|
19150
19165
|
"/export - Export the current session as a markdown file\n" +
|
|
19151
19166
|
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
19152
19167
|
"/verbose - Toggle verbose mode (show thinking and tool calls)\n" +
|
|
@@ -19182,16 +19197,9 @@ async function startTelegram(options) {
|
|
|
19182
19197
|
.sort((a, b) => b.time.updated - a.time.updated);
|
|
19183
19198
|
}
|
|
19184
19199
|
/**
|
|
19185
|
-
* Resolve a user argument to a session
|
|
19186
|
-
* (1-based, as shown by /sessions) or a session ID / prefix.
|
|
19200
|
+
* Resolve a user argument to a session ID / prefix.
|
|
19187
19201
|
*/
|
|
19188
19202
|
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
19203
|
return sessions.find((s) => s.id === arg || s.id.startsWith(arg));
|
|
19196
19204
|
}
|
|
19197
19205
|
// Handle /sessions command - list Telegram bot sessions only
|
|
@@ -19204,28 +19212,48 @@ async function startTelegram(options) {
|
|
|
19204
19212
|
await ctx.reply("No sessions found.");
|
|
19205
19213
|
return;
|
|
19206
19214
|
}
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19215
|
+
const activeSession = sessions.find((session) => session.id === activeSessionId);
|
|
19216
|
+
const msgLines = ["Your sessions:"];
|
|
19217
|
+
if (activeSession) {
|
|
19218
|
+
msgLines.push(`Current session: ${activeSession.title}`);
|
|
19219
|
+
}
|
|
19220
|
+
const otherSessions = sessions.filter((session) => session.id !== activeSessionId);
|
|
19221
|
+
if (otherSessions.length === 0) {
|
|
19222
|
+
msgLines.push("This is your only session.");
|
|
19223
|
+
await ctx.reply(msgLines.join("\n"));
|
|
19224
|
+
return;
|
|
19225
|
+
}
|
|
19226
|
+
msgLines.push("Tap a session to switch or delete.");
|
|
19227
|
+
const keyboard = [];
|
|
19228
|
+
otherSessions.forEach((session) => {
|
|
19229
|
+
keyboard.push([
|
|
19230
|
+
{
|
|
19231
|
+
text: session.title,
|
|
19232
|
+
callback_data: `switch:${session.id}`,
|
|
19233
|
+
},
|
|
19234
|
+
{
|
|
19235
|
+
text: "Delete",
|
|
19236
|
+
callback_data: `delete:${session.id}`,
|
|
19237
|
+
},
|
|
19238
|
+
]);
|
|
19239
|
+
});
|
|
19240
|
+
await ctx.reply(msgLines.join("\n"), {
|
|
19241
|
+
reply_markup: {
|
|
19242
|
+
inline_keyboard: keyboard,
|
|
19243
|
+
},
|
|
19213
19244
|
});
|
|
19214
|
-
msg += `\nUse /switch <number> to switch sessions.`;
|
|
19215
|
-
msg += `\nUse /delete <number> to delete a session.`;
|
|
19216
|
-
await ctx.reply(msg);
|
|
19217
19245
|
}
|
|
19218
19246
|
catch (err) {
|
|
19219
19247
|
console.error("[Telegram] Error listing sessions:", err);
|
|
19220
19248
|
await ctx.reply("Failed to list sessions.");
|
|
19221
19249
|
}
|
|
19222
19250
|
});
|
|
19223
|
-
// Handle /switch <
|
|
19251
|
+
// Handle /switch <id> command - switch to a different session
|
|
19224
19252
|
bot.command("switch", async (ctx) => {
|
|
19225
19253
|
const chatId = ctx.chat.id.toString();
|
|
19226
19254
|
const args = ctx.message.text.replace(/^\/switch\s*/, "").trim();
|
|
19227
19255
|
if (!args) {
|
|
19228
|
-
await ctx.reply("Usage: /switch <
|
|
19256
|
+
await ctx.reply("Usage: /switch <session id>\n\nUse /sessions to see available sessions.");
|
|
19229
19257
|
return;
|
|
19230
19258
|
}
|
|
19231
19259
|
try {
|
|
@@ -19274,12 +19302,12 @@ async function startTelegram(options) {
|
|
|
19274
19302
|
await ctx.reply("Failed to rename session.");
|
|
19275
19303
|
}
|
|
19276
19304
|
});
|
|
19277
|
-
// Handle /delete <
|
|
19305
|
+
// Handle /delete <id> command - delete a session
|
|
19278
19306
|
bot.command("delete", async (ctx) => {
|
|
19279
19307
|
const chatId = ctx.chat.id.toString();
|
|
19280
19308
|
const args = ctx.message.text.replace(/^\/delete\s*/, "").trim();
|
|
19281
19309
|
if (!args) {
|
|
19282
|
-
await ctx.reply("Usage: /delete <
|
|
19310
|
+
await ctx.reply("Usage: /delete <session id>\n\nUse /sessions to see available sessions.");
|
|
19283
19311
|
return;
|
|
19284
19312
|
}
|
|
19285
19313
|
try {
|
|
@@ -19313,6 +19341,61 @@ async function startTelegram(options) {
|
|
|
19313
19341
|
await ctx.reply("Failed to delete session.");
|
|
19314
19342
|
}
|
|
19315
19343
|
});
|
|
19344
|
+
bot.action(/^switch:(.+)$/, async (ctx) => {
|
|
19345
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19346
|
+
const sessionId = ctx.match?.[1];
|
|
19347
|
+
if (!chatId || !sessionId)
|
|
19348
|
+
return;
|
|
19349
|
+
try {
|
|
19350
|
+
const sessions = await getKnownSessions();
|
|
19351
|
+
const match = sessions.find((session) => session.id === sessionId);
|
|
19352
|
+
if (!match) {
|
|
19353
|
+
await answerAndEdit(ctx, "Session not found. Use /sessions to refresh.");
|
|
19354
|
+
return;
|
|
19355
|
+
}
|
|
19356
|
+
chatSessions.set(chatId, match.id);
|
|
19357
|
+
saveSessions();
|
|
19358
|
+
console.log(`[Telegram] Switched chat ${chatId} to session ${match.id}`);
|
|
19359
|
+
await answerAndEdit(ctx, `Switched to session: ${match.title}`);
|
|
19360
|
+
}
|
|
19361
|
+
catch (err) {
|
|
19362
|
+
console.error("[Telegram] Error switching session:", err);
|
|
19363
|
+
await answerAndEdit(ctx, "Failed to switch session.");
|
|
19364
|
+
}
|
|
19365
|
+
});
|
|
19366
|
+
bot.action(/^delete:(.+)$/, async (ctx) => {
|
|
19367
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19368
|
+
const sessionId = ctx.match?.[1];
|
|
19369
|
+
if (!chatId || !sessionId)
|
|
19370
|
+
return;
|
|
19371
|
+
try {
|
|
19372
|
+
const sessions = await getKnownSessions();
|
|
19373
|
+
const match = sessions.find((session) => session.id === sessionId);
|
|
19374
|
+
if (!match) {
|
|
19375
|
+
await answerAndEdit(ctx, "Session not found. Use /sessions to refresh.");
|
|
19376
|
+
return;
|
|
19377
|
+
}
|
|
19378
|
+
const activeSessionId = chatSessions.get(chatId);
|
|
19379
|
+
if (match.id === activeSessionId) {
|
|
19380
|
+
await answerAndEdit(ctx, "Cannot delete the active session. Use /new or /switch first, then delete it.");
|
|
19381
|
+
return;
|
|
19382
|
+
}
|
|
19383
|
+
const result = await client.session.delete({
|
|
19384
|
+
path: { id: match.id },
|
|
19385
|
+
});
|
|
19386
|
+
if (result.error) {
|
|
19387
|
+
throw new Error(JSON.stringify(result.error));
|
|
19388
|
+
}
|
|
19389
|
+
knownSessionIds.delete(match.id);
|
|
19390
|
+
saveSessions();
|
|
19391
|
+
console.log(`[Telegram] Deleted session ${match.id}`);
|
|
19392
|
+
await answerAndEdit(ctx, `Deleted session: ${match.title}`);
|
|
19393
|
+
}
|
|
19394
|
+
catch (err) {
|
|
19395
|
+
console.error("[Telegram] Error deleting session:", err);
|
|
19396
|
+
await answerAndEdit(ctx, "Failed to delete session.");
|
|
19397
|
+
}
|
|
19398
|
+
});
|
|
19316
19399
|
// Handle /verbose command - toggle verbose mode for this chat
|
|
19317
19400
|
// Usage: /verbose, /verbose on, /verbose off
|
|
19318
19401
|
bot.command("verbose", (ctx) => {
|
|
@@ -19368,7 +19451,7 @@ async function startTelegram(options) {
|
|
|
19368
19451
|
return results;
|
|
19369
19452
|
}
|
|
19370
19453
|
// Handle /model command
|
|
19371
|
-
// Usage: /model, /model <keyword>, /model
|
|
19454
|
+
// Usage: /model, /model <keyword>, /model default
|
|
19372
19455
|
bot.command("model", async (ctx) => {
|
|
19373
19456
|
const chatId = ctx.chat.id.toString();
|
|
19374
19457
|
const args = ctx.message.text.replace(/^\/model\s*/, "").trim();
|
|
@@ -19385,24 +19468,6 @@ async function startTelegram(options) {
|
|
|
19385
19468
|
await ctx.reply("Model reset to the default model.");
|
|
19386
19469
|
return;
|
|
19387
19470
|
}
|
|
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
19471
|
try {
|
|
19407
19472
|
const results = await searchModels(args);
|
|
19408
19473
|
if (results.length === 0) {
|
|
@@ -19411,21 +19476,60 @@ async function startTelegram(options) {
|
|
|
19411
19476
|
}
|
|
19412
19477
|
const limited = results.slice(0, 10);
|
|
19413
19478
|
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
|
-
}
|
|
19479
|
+
let msg = `Models matching "${args}":`;
|
|
19418
19480
|
if (results.length > limited.length) {
|
|
19419
19481
|
msg += `\nFound ${results.length} models. Refine your search to narrow the list.`;
|
|
19420
19482
|
}
|
|
19421
|
-
msg += "\
|
|
19422
|
-
|
|
19483
|
+
msg += "\nTap a model to select.";
|
|
19484
|
+
const keyboard = limited.map((item, index) => [
|
|
19485
|
+
{
|
|
19486
|
+
text: item.displayName,
|
|
19487
|
+
callback_data: `model:${index + 1}`,
|
|
19488
|
+
},
|
|
19489
|
+
]);
|
|
19490
|
+
keyboard.push([
|
|
19491
|
+
{ text: "Reset to default", callback_data: "model_default" },
|
|
19492
|
+
]);
|
|
19493
|
+
await ctx.reply(msg, {
|
|
19494
|
+
reply_markup: {
|
|
19495
|
+
inline_keyboard: keyboard,
|
|
19496
|
+
},
|
|
19497
|
+
});
|
|
19423
19498
|
}
|
|
19424
19499
|
catch (err) {
|
|
19425
19500
|
console.error("[Telegram] Error searching models:", err);
|
|
19426
19501
|
await ctx.reply("Failed to list models. Try again later.");
|
|
19427
19502
|
}
|
|
19428
19503
|
});
|
|
19504
|
+
bot.action(/^model:(\d+)$/, async (ctx) => {
|
|
19505
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19506
|
+
const indexText = ctx.match?.[1];
|
|
19507
|
+
if (!chatId || !indexText)
|
|
19508
|
+
return;
|
|
19509
|
+
const selectionIndex = Number.parseInt(indexText, 10);
|
|
19510
|
+
const results = chatModelSearchResults.get(chatId) || [];
|
|
19511
|
+
if (results.length === 0) {
|
|
19512
|
+
await answerAndEdit(ctx, "No recent search results. Use /model <keyword> first.");
|
|
19513
|
+
return;
|
|
19514
|
+
}
|
|
19515
|
+
if (selectionIndex < 1 || selectionIndex > results.length) {
|
|
19516
|
+
await answerAndEdit(ctx, "Invalid selection. Use the latest model search results.");
|
|
19517
|
+
return;
|
|
19518
|
+
}
|
|
19519
|
+
const selection = results[selectionIndex - 1];
|
|
19520
|
+
const value = `${selection.providerID}/${selection.modelID}`;
|
|
19521
|
+
chatModelOverride.set(chatId, value);
|
|
19522
|
+
saveSessions();
|
|
19523
|
+
await answerAndEdit(ctx, `Switched to ${selection.displayName}`);
|
|
19524
|
+
});
|
|
19525
|
+
bot.action("model_default", async (ctx) => {
|
|
19526
|
+
const chatId = getChatIdFromContext(ctx);
|
|
19527
|
+
if (!chatId)
|
|
19528
|
+
return;
|
|
19529
|
+
chatModelOverride.delete(chatId);
|
|
19530
|
+
saveSessions();
|
|
19531
|
+
await answerAndEdit(ctx, "Model reset to the default model.");
|
|
19532
|
+
});
|
|
19429
19533
|
// Handle /usage command - show token and cost usage for current session
|
|
19430
19534
|
bot.command("usage", async (ctx) => {
|
|
19431
19535
|
const chatId = ctx.chat.id.toString();
|
|
@@ -19804,22 +19908,6 @@ async function startTelegram(options) {
|
|
|
19804
19908
|
}
|
|
19805
19909
|
return bot;
|
|
19806
19910
|
}
|
|
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
19911
|
/**
|
|
19824
19912
|
* Truncate text to a maximum length, appending "..." if truncated.
|
|
19825
19913
|
*/
|