@inetafrica/open-claudia 1.14.9 → 1.15.0
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/bot.js +55 -3
- package/package.json +1 -1
package/bot.js
CHANGED
|
@@ -274,6 +274,7 @@ bot.setMyCommands([
|
|
|
274
274
|
{ command: "ask", description: "Toggle ask mode (Cursor only)" },
|
|
275
275
|
{ command: "sessions", description: "List conversations for this project" },
|
|
276
276
|
{ command: "compact", description: "Summarize conversation context" },
|
|
277
|
+
{ command: "usage", description: "Show token usage & cost for this session" },
|
|
277
278
|
{ command: "continue", description: "Resume last conversation" },
|
|
278
279
|
{ command: "worktree", description: "Toggle isolated git branch" },
|
|
279
280
|
{ command: "cron", description: "Manage scheduled tasks" },
|
|
@@ -325,6 +326,7 @@ function saveState() {
|
|
|
325
326
|
lastSessionId,
|
|
326
327
|
cursorSessionId,
|
|
327
328
|
settings,
|
|
329
|
+
sessionUsage,
|
|
328
330
|
};
|
|
329
331
|
try { fs.writeFileSync(STATE_FILE, JSON.stringify(data)); } catch (e) {}
|
|
330
332
|
}
|
|
@@ -402,6 +404,20 @@ let pendingClaudeAuthLabel = null;
|
|
|
402
404
|
let isFirstMessage = !lastSessionId; // Track if this is first message in session
|
|
403
405
|
let lastInputWasVoice = false; // Reply with voice when input was voice
|
|
404
406
|
|
|
407
|
+
// Per-session usage tracking (resets on /end or new session)
|
|
408
|
+
let sessionUsage = savedState.sessionUsage || {
|
|
409
|
+
turns: 0,
|
|
410
|
+
inputTokens: 0,
|
|
411
|
+
outputTokens: 0,
|
|
412
|
+
cacheReadTokens: 0,
|
|
413
|
+
cacheCreationTokens: 0,
|
|
414
|
+
costUsd: 0,
|
|
415
|
+
lastInputTokens: 0, // Last turn's input tokens (for compact suggestion)
|
|
416
|
+
};
|
|
417
|
+
function resetSessionUsage() {
|
|
418
|
+
sessionUsage = { turns: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, costUsd: 0, lastInputTokens: 0 };
|
|
419
|
+
}
|
|
420
|
+
|
|
405
421
|
let settings = savedState.settings || {
|
|
406
422
|
model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude",
|
|
407
423
|
};
|
|
@@ -1324,6 +1340,17 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1324
1340
|
if (evt.type === "result" && evt.session_id) {
|
|
1325
1341
|
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
1326
1342
|
else { lastSessionId = evt.session_id; }
|
|
1343
|
+
if (evt.usage) {
|
|
1344
|
+
sessionUsage.turns += 1;
|
|
1345
|
+
sessionUsage.inputTokens += evt.usage.input_tokens || 0;
|
|
1346
|
+
sessionUsage.outputTokens += evt.usage.output_tokens || 0;
|
|
1347
|
+
sessionUsage.cacheReadTokens += evt.usage.cache_read_input_tokens || 0;
|
|
1348
|
+
sessionUsage.cacheCreationTokens += evt.usage.cache_creation_input_tokens || 0;
|
|
1349
|
+
sessionUsage.lastInputTokens = (evt.usage.input_tokens || 0) +
|
|
1350
|
+
(evt.usage.cache_read_input_tokens || 0) +
|
|
1351
|
+
(evt.usage.cache_creation_input_tokens || 0);
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof evt.total_cost_usd === "number") sessionUsage.costUsd += evt.total_cost_usd;
|
|
1327
1354
|
saveState();
|
|
1328
1355
|
}
|
|
1329
1356
|
if (evt.type === "result" && evt.result) assistantText = evt.result;
|
|
@@ -1384,6 +1411,15 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1384
1411
|
const voicePath = textToVoice(finalText);
|
|
1385
1412
|
if (voicePath) await sendVoice(voicePath);
|
|
1386
1413
|
}
|
|
1414
|
+
|
|
1415
|
+
// Suggest /compact when context gets large (once per threshold crossing)
|
|
1416
|
+
const ctx = sessionUsage.lastInputTokens;
|
|
1417
|
+
if (ctx > 150000 && !sessionUsage.warnedHighContext) {
|
|
1418
|
+
sessionUsage.warnedHighContext = true;
|
|
1419
|
+
await send(`Heads up: context is ${(ctx / 1000).toFixed(0)}k tokens. Use /compact to summarize or /end for a fresh start — keeps cost down.`);
|
|
1420
|
+
} else if (ctx < 80000 && sessionUsage.warnedHighContext) {
|
|
1421
|
+
sessionUsage.warnedHighContext = false;
|
|
1422
|
+
}
|
|
1387
1423
|
} catch (e) {
|
|
1388
1424
|
console.error("Final message delivery failed:", e.message);
|
|
1389
1425
|
await send("Task completed but failed to deliver the response. Send /continue to see the result.");
|
|
@@ -1760,12 +1796,28 @@ bot.onText(/\/status/, (msg) => {
|
|
|
1760
1796
|
bot.onText(/\/end/, (msg) => {
|
|
1761
1797
|
if (!isAuthorized(msg)) return;
|
|
1762
1798
|
if (currentSession) {
|
|
1763
|
-
const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings();
|
|
1799
|
+
const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); resetSessionUsage();
|
|
1764
1800
|
saveState();
|
|
1765
1801
|
send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New session", callback_data: "show:projects" }]] } });
|
|
1766
1802
|
} else send("No session.");
|
|
1767
1803
|
});
|
|
1768
1804
|
|
|
1805
|
+
bot.onText(/\/usage$/, (msg) => {
|
|
1806
|
+
if (!isAuthorized(msg)) return;
|
|
1807
|
+
const u = sessionUsage;
|
|
1808
|
+
if (u.turns === 0) return send("No usage yet in this session.");
|
|
1809
|
+
const fmt = (n) => n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n);
|
|
1810
|
+
const lines = [
|
|
1811
|
+
`*Session usage* (${u.turns} turn${u.turns === 1 ? "" : "s"})`,
|
|
1812
|
+
`Input: ${fmt(u.inputTokens)} · Output: ${fmt(u.outputTokens)}`,
|
|
1813
|
+
`Cache read: ${fmt(u.cacheReadTokens)} · Cache write: ${fmt(u.cacheCreationTokens)}`,
|
|
1814
|
+
`Cost: $${u.costUsd.toFixed(4)}`,
|
|
1815
|
+
`Last turn context: ${fmt(u.lastInputTokens)}`,
|
|
1816
|
+
];
|
|
1817
|
+
if (u.lastInputTokens > 100000) lines.push("\nTip: context is large. Use /compact to summarize, or /end for a clean slate.");
|
|
1818
|
+
send(lines.join("\n"), { parseMode: "Markdown" });
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1769
1821
|
bot.onText(/\/soul$/, async (msg) => {
|
|
1770
1822
|
if (!isAuthorized(msg)) return;
|
|
1771
1823
|
const soul = loadSoul();
|
|
@@ -1965,13 +2017,13 @@ bot.on("callback_query", async (q) => {
|
|
|
1965
2017
|
const sessions = loadSessions();
|
|
1966
2018
|
// Don't delete history, just start fresh
|
|
1967
2019
|
currentSession = { name: proj === "__workspace__" ? "Workspace" : proj, dir: proj === "__workspace__" ? WORKSPACE : path.join(WORKSPACE, proj) };
|
|
1968
|
-
lastSessionId = null; isFirstMessage = true; messageQueue = []; resetSettings();
|
|
2020
|
+
lastSessionId = null; isFirstMessage = true; messageQueue = []; resetSettings(); resetSessionUsage();
|
|
1969
2021
|
saveState();
|
|
1970
2022
|
await send(`Session: ${currentSession.name}\nNew conversation\n\nSend text, voice, or images.\n\n/sessions — switch conversation\n/session — switch project`);
|
|
1971
2023
|
return;
|
|
1972
2024
|
}
|
|
1973
2025
|
if (d === "a:continue") { if (currentSession) await runClaude("continue", currentSession.dir); else send("No session."); return; }
|
|
1974
|
-
if (d === "a:end") { if (currentSession) { const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); saveState(); await send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New", callback_data: "show:projects" }]] } }); } return; }
|
|
2026
|
+
if (d === "a:end") { if (currentSession) { const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); resetSessionUsage(); saveState(); await send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New", callback_data: "show:projects" }]] } }); } return; }
|
|
1975
2027
|
if (d.startsWith("m:")) { settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${settings.model || "default"}`); return; }
|
|
1976
2028
|
if (d.startsWith("e:")) { const e = d.slice(2); settings.effort = e === "default" ? null : e; await send(`Effort: ${settings.effort || "default"}`); return; }
|
|
1977
2029
|
if (d.startsWith("b:")) { const b = d.slice(2); settings.budget = b === "none" ? null : parseFloat(b); await send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`); return; }
|