@grinev/opencode-telegram-bot 0.12.0 → 0.12.1
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/dist/bot/index.js +23 -2
- package/dist/i18n/de.js +1 -0
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/es.js +1 -0
- package/dist/i18n/fr.js +1 -0
- package/dist/i18n/ru.js +1 -0
- package/dist/i18n/zh.js +1 -0
- package/dist/pinned/manager.js +23 -3
- package/dist/summary/aggregator.js +9 -0
- package/package.json +1 -1
package/dist/bot/index.js
CHANGED
|
@@ -61,6 +61,12 @@ const SESSION_RETRY_PREFIX = "🔁";
|
|
|
61
61
|
const __filename = fileURLToPath(import.meta.url);
|
|
62
62
|
const __dirname = path.dirname(__filename);
|
|
63
63
|
const TEMP_DIR = path.join(__dirname, "..", ".tmp");
|
|
64
|
+
function getCurrentReplyKeyboard() {
|
|
65
|
+
if (!keyboardManager.isInitialized()) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return keyboardManager.getKeyboard();
|
|
69
|
+
}
|
|
64
70
|
function prepareDocumentCaption(caption) {
|
|
65
71
|
const normalizedCaption = caption.trim();
|
|
66
72
|
if (!normalizedCaption) {
|
|
@@ -81,8 +87,10 @@ const toolMessageBatcher = new ToolMessageBatcher({
|
|
|
81
87
|
if (!currentSession || currentSession.id !== sessionId) {
|
|
82
88
|
return;
|
|
83
89
|
}
|
|
90
|
+
const keyboard = getCurrentReplyKeyboard();
|
|
84
91
|
await botInstance.api.sendMessage(chatIdInstance, text, {
|
|
85
92
|
disable_notification: true,
|
|
93
|
+
...(keyboard ? { reply_markup: keyboard } : {}),
|
|
86
94
|
});
|
|
87
95
|
},
|
|
88
96
|
sendFile: async (sessionId, fileData) => {
|
|
@@ -98,9 +106,11 @@ const toolMessageBatcher = new ToolMessageBatcher({
|
|
|
98
106
|
logger.debug(`[Bot] Sending code file: ${fileData.filename} (${fileData.buffer.length} bytes, session=${sessionId})`);
|
|
99
107
|
await fs.mkdir(TEMP_DIR, { recursive: true });
|
|
100
108
|
await fs.writeFile(tempFilePath, fileData.buffer);
|
|
109
|
+
const keyboard = getCurrentReplyKeyboard();
|
|
101
110
|
await botInstance.api.sendDocument(chatIdInstance, new InputFile(tempFilePath), {
|
|
102
111
|
caption: fileData.caption,
|
|
103
112
|
disable_notification: true,
|
|
113
|
+
...(keyboard ? { reply_markup: keyboard } : {}),
|
|
104
114
|
});
|
|
105
115
|
}
|
|
106
116
|
finally {
|
|
@@ -161,8 +171,7 @@ async function ensureEventSubscription(directory) {
|
|
|
161
171
|
const assistantMessageFormat = assistantParseMode === "MarkdownV2" ? "markdown_v2" : "raw";
|
|
162
172
|
logger.debug(`[Bot] Sending completed message to Telegram (chatId=${chatIdInstance}, parts=${parts.length})`);
|
|
163
173
|
for (let i = 0; i < parts.length; i++) {
|
|
164
|
-
const
|
|
165
|
-
const keyboard = isLastPart && keyboardManager.isInitialized() ? keyboardManager.getKeyboard() : undefined;
|
|
174
|
+
const keyboard = getCurrentReplyKeyboard();
|
|
166
175
|
const options = keyboard ? { reply_markup: keyboard } : undefined;
|
|
167
176
|
await sendBotText({
|
|
168
177
|
api: botInstance.api,
|
|
@@ -305,6 +314,18 @@ async function ensureEventSubscription(directory) {
|
|
|
305
314
|
logger.error("[Bot] Error updating pinned message with tokens:", err);
|
|
306
315
|
}
|
|
307
316
|
});
|
|
317
|
+
summaryAggregator.setOnCost(async (cost) => {
|
|
318
|
+
if (!pinnedMessageManager.isInitialized()) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
logger.debug(`[Bot] Cost update: $${cost.toFixed(2)}`);
|
|
323
|
+
await pinnedMessageManager.onCostUpdate(cost);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
logger.error("[Bot] Error updating cost:", err);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
308
329
|
summaryAggregator.setOnSessionCompacted(async (sessionId, directory) => {
|
|
309
330
|
if (!pinnedMessageManager.isInitialized()) {
|
|
310
331
|
return;
|
package/dist/i18n/de.js
CHANGED
|
@@ -216,6 +216,7 @@ export const de = {
|
|
|
216
216
|
"pinned.line.project": "Projekt: {project}",
|
|
217
217
|
"pinned.line.model": "Modell: {model}",
|
|
218
218
|
"pinned.line.context": "Kontext: {used} / {limit} ({percent}%)",
|
|
219
|
+
"pinned.line.cost": "Kosten: {cost} ausgegeben",
|
|
219
220
|
"pinned.files.title": "Dateien ({count}):",
|
|
220
221
|
"pinned.files.item": " {path}{diff}",
|
|
221
222
|
"pinned.files.more": " ... und {count} mehr",
|
package/dist/i18n/en.js
CHANGED
|
@@ -216,6 +216,7 @@ export const en = {
|
|
|
216
216
|
"pinned.line.project": "Project: {project}",
|
|
217
217
|
"pinned.line.model": "Model: {model}",
|
|
218
218
|
"pinned.line.context": "Context: {used} / {limit} ({percent}%)",
|
|
219
|
+
"pinned.line.cost": "Cost: {cost} spent",
|
|
219
220
|
"pinned.files.title": "Files ({count}):",
|
|
220
221
|
"pinned.files.item": " {path}{diff}",
|
|
221
222
|
"pinned.files.more": " ... and {count} more",
|
package/dist/i18n/es.js
CHANGED
|
@@ -216,6 +216,7 @@ export const es = {
|
|
|
216
216
|
"pinned.line.project": "Proyecto: {project}",
|
|
217
217
|
"pinned.line.model": "Modelo: {model}",
|
|
218
218
|
"pinned.line.context": "Contexto: {used} / {limit} ({percent}%)",
|
|
219
|
+
"pinned.line.cost": "Costo: {cost} gastado",
|
|
219
220
|
"pinned.files.title": "Archivos ({count}):",
|
|
220
221
|
"pinned.files.item": " {path}{diff}",
|
|
221
222
|
"pinned.files.more": " ... y {count} más",
|
package/dist/i18n/fr.js
CHANGED
|
@@ -216,6 +216,7 @@ export const fr = {
|
|
|
216
216
|
"pinned.line.project": "Projet : {project}",
|
|
217
217
|
"pinned.line.model": "Modèle : {model}",
|
|
218
218
|
"pinned.line.context": "Contexte : {used} / {limit} ({percent}%)",
|
|
219
|
+
"pinned.line.cost": "Coût : {cost} dépensé",
|
|
219
220
|
"pinned.files.title": "Fichiers ({count}) :",
|
|
220
221
|
"pinned.files.item": " {path}{diff}",
|
|
221
222
|
"pinned.files.more": " ... et encore {count}",
|
package/dist/i18n/ru.js
CHANGED
|
@@ -216,6 +216,7 @@ export const ru = {
|
|
|
216
216
|
"pinned.line.project": "Проект: {project}",
|
|
217
217
|
"pinned.line.model": "Модель: {model}",
|
|
218
218
|
"pinned.line.context": "Контекст: {used} / {limit} ({percent}%)",
|
|
219
|
+
"pinned.line.cost": "Стоимость: {cost} потрачено",
|
|
219
220
|
"pinned.files.title": "Файлы ({count}):",
|
|
220
221
|
"pinned.files.item": " {path}{diff}",
|
|
221
222
|
"pinned.files.more": " ... и еще {count}",
|
package/dist/i18n/zh.js
CHANGED
|
@@ -216,6 +216,7 @@ export const zh = {
|
|
|
216
216
|
"pinned.line.project": "项目: {project}",
|
|
217
217
|
"pinned.line.model": "模型: {model}",
|
|
218
218
|
"pinned.line.context": "上下文: {used} / {limit} ({percent}%)",
|
|
219
|
+
"pinned.line.cost": "费用: {cost}",
|
|
219
220
|
"pinned.files.title": "文件({count}):",
|
|
220
221
|
"pinned.files.item": " {path}{diff}",
|
|
221
222
|
"pinned.files.more": " ... 还有 {count} 个",
|
package/dist/pinned/manager.js
CHANGED
|
@@ -17,6 +17,7 @@ class PinnedMessageManager {
|
|
|
17
17
|
tokensLimit: 0,
|
|
18
18
|
lastUpdated: 0,
|
|
19
19
|
changedFiles: [],
|
|
20
|
+
cost: 0,
|
|
20
21
|
};
|
|
21
22
|
contextLimit = null;
|
|
22
23
|
onKeyboardUpdateCallback;
|
|
@@ -40,6 +41,7 @@ class PinnedMessageManager {
|
|
|
40
41
|
logger.info(`[PinnedManager] Session changed: ${sessionId}, title: ${sessionTitle}`);
|
|
41
42
|
// Reset tokens for new session
|
|
42
43
|
this.state.tokensUsed = 0;
|
|
44
|
+
this.state.cost = 0;
|
|
43
45
|
// Update state
|
|
44
46
|
this.state.sessionId = sessionId;
|
|
45
47
|
this.state.sessionTitle = sessionTitle || t("pinned.default_session_title");
|
|
@@ -84,9 +86,10 @@ class PinnedMessageManager {
|
|
|
84
86
|
logger.warn("[PinnedManager] Failed to load session history:", error);
|
|
85
87
|
return;
|
|
86
88
|
}
|
|
87
|
-
// Get the maximum context size from session history
|
|
89
|
+
// Get the maximum context size and cost from session history
|
|
88
90
|
// Context = input + cache.read (cache.read contains previously cached context)
|
|
89
91
|
let maxContextSize = 0;
|
|
92
|
+
let maxCost = 0;
|
|
90
93
|
logger.debug(`[PinnedManager] Processing ${messagesData.length} messages from history`);
|
|
91
94
|
messagesData.forEach(({ info }) => {
|
|
92
95
|
if (info.role === "assistant") {
|
|
@@ -99,16 +102,22 @@ class PinnedMessageManager {
|
|
|
99
102
|
const input = assistantInfo.tokens?.input || 0;
|
|
100
103
|
const cacheRead = assistantInfo.tokens?.cache?.read || 0;
|
|
101
104
|
const contextSize = input + cacheRead;
|
|
102
|
-
|
|
105
|
+
const cost = assistantInfo.cost || 0;
|
|
106
|
+
logger.debug(`[PinnedManager] Assistant message: input=${input}, cache.read=${cacheRead}, total=${contextSize}, cost=$${cost.toFixed(2)}`);
|
|
103
107
|
// Keep track of maximum context size (peak usage in session)
|
|
104
108
|
if (contextSize > maxContextSize) {
|
|
105
109
|
maxContextSize = contextSize;
|
|
106
110
|
}
|
|
111
|
+
// Keep track of maximum cost (cumulative in OpenCode)
|
|
112
|
+
if (cost > maxCost) {
|
|
113
|
+
maxCost = cost;
|
|
114
|
+
}
|
|
107
115
|
}
|
|
108
116
|
});
|
|
109
117
|
this.state.tokensUsed = maxContextSize;
|
|
118
|
+
this.state.cost = maxCost;
|
|
110
119
|
this.state.sessionId = sessionId;
|
|
111
|
-
logger.info(`[PinnedManager] Loaded context from history: ${this.state.tokensUsed} tokens`);
|
|
120
|
+
logger.info(`[PinnedManager] Loaded context from history: ${this.state.tokensUsed} tokens, cost: $${this.state.cost.toFixed(2)}`);
|
|
112
121
|
await this.updatePinnedMessage();
|
|
113
122
|
}
|
|
114
123
|
catch (err) {
|
|
@@ -140,6 +149,14 @@ class PinnedMessageManager {
|
|
|
140
149
|
await this.refreshSessionTitle();
|
|
141
150
|
await this.updatePinnedMessage();
|
|
142
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Called when cost info is received from SSE events
|
|
154
|
+
*/
|
|
155
|
+
async onCostUpdate(cost) {
|
|
156
|
+
this.state.cost = cost;
|
|
157
|
+
logger.debug(`[PinnedManager] Cost updated: $${cost.toFixed(2)}`);
|
|
158
|
+
await this.updatePinnedMessage();
|
|
159
|
+
}
|
|
143
160
|
/**
|
|
144
161
|
* Set callback for keyboard updates when context changes
|
|
145
162
|
*/
|
|
@@ -455,6 +472,9 @@ class PinnedMessageManager {
|
|
|
455
472
|
percent: percentage,
|
|
456
473
|
}),
|
|
457
474
|
];
|
|
475
|
+
if (this.state.cost !== undefined && this.state.cost !== null) {
|
|
476
|
+
lines.push(t("pinned.line.cost", { cost: `$${this.state.cost.toFixed(2)}` }));
|
|
477
|
+
}
|
|
458
478
|
if (this.state.changedFiles.length > 0) {
|
|
459
479
|
const maxFiles = 10;
|
|
460
480
|
const total = this.state.changedFiles.length;
|
|
@@ -38,6 +38,7 @@ class SummaryAggregator {
|
|
|
38
38
|
onQuestionErrorCallback = null;
|
|
39
39
|
onThinkingCallback = null;
|
|
40
40
|
onTokensCallback = null;
|
|
41
|
+
onCostCallback = null;
|
|
41
42
|
onSessionCompactedCallback = null;
|
|
42
43
|
onSessionErrorCallback = null;
|
|
43
44
|
onSessionRetryCallback = null;
|
|
@@ -76,6 +77,9 @@ class SummaryAggregator {
|
|
|
76
77
|
setOnTokens(callback) {
|
|
77
78
|
this.onTokensCallback = callback;
|
|
78
79
|
}
|
|
80
|
+
setOnCost(callback) {
|
|
81
|
+
this.onCostCallback = callback;
|
|
82
|
+
}
|
|
79
83
|
setOnSessionCompacted(callback) {
|
|
80
84
|
this.onSessionCompactedCallback = callback;
|
|
81
85
|
}
|
|
@@ -231,6 +235,11 @@ class SummaryAggregator {
|
|
|
231
235
|
// Call synchronously so keyboardManager is updated before onComplete sends the reply
|
|
232
236
|
this.onTokensCallback(tokens);
|
|
233
237
|
}
|
|
238
|
+
// Extract and report cost
|
|
239
|
+
if (this.onCostCallback && assistantInfo.cost !== undefined) {
|
|
240
|
+
logger.debug(`[Aggregator] Cost: $${assistantInfo.cost.toFixed(2)}`);
|
|
241
|
+
this.onCostCallback(assistantInfo.cost);
|
|
242
|
+
}
|
|
234
243
|
if (this.onCompleteCallback && lastPart.length > 0) {
|
|
235
244
|
this.onCompleteCallback(this.currentSessionId, lastPart);
|
|
236
245
|
}
|