codeclaw 0.2.6 → 0.2.8
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-telegram.js +60 -0
- package/dist/bot.js +12 -2
- package/dist/code-agent.js +103 -1
- package/package.json +1 -1
package/dist/bot-telegram.js
CHANGED
|
@@ -12,6 +12,24 @@ import { Bot, VERSION, fmtTokens, fmtUptime, fmtBytes, whichSync, listSubdirs, b
|
|
|
12
12
|
import { TelegramChannel } from './channel-telegram.js';
|
|
13
13
|
import { splitText } from './channel-base.js';
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
+
// Context window sizes (max input tokens per model family)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
function getContextWindowSize(model) {
|
|
18
|
+
if (!model)
|
|
19
|
+
return null;
|
|
20
|
+
const m = model.toLowerCase();
|
|
21
|
+
// Claude models: 200k context
|
|
22
|
+
if (m.includes('claude'))
|
|
23
|
+
return 200_000;
|
|
24
|
+
// GPT-4.1 / GPT-5 / o3/o4 family: 200k (1M for o3, but default to 200k)
|
|
25
|
+
if (m.startsWith('gpt-') || m.startsWith('o3') || m.startsWith('o4'))
|
|
26
|
+
return 200_000;
|
|
27
|
+
// Gemini: 1M context
|
|
28
|
+
if (m.includes('gemini'))
|
|
29
|
+
return 1_000_000;
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
15
33
|
// Telegram HTML formatting
|
|
16
34
|
// ---------------------------------------------------------------------------
|
|
17
35
|
function escapeHtml(t) {
|
|
@@ -321,6 +339,7 @@ export class TelegramBot extends Bot {
|
|
|
321
339
|
static MENU_COMMANDS = [
|
|
322
340
|
{ command: 'sessions', description: 'List / switch sessions' },
|
|
323
341
|
{ command: 'agents', description: 'List / switch agents' },
|
|
342
|
+
{ command: 'models', description: 'List / switch models' },
|
|
324
343
|
{ command: 'status', description: 'Bot status' },
|
|
325
344
|
{ command: 'host', description: 'Host machine info' },
|
|
326
345
|
{ command: 'switch', description: 'Switch working directory' },
|
|
@@ -336,6 +355,7 @@ export class TelegramBot extends Bot {
|
|
|
336
355
|
await ctx.reply(`<b>codeclaw</b> v${VERSION}\n\n` +
|
|
337
356
|
`/sessions \u2014 List / switch sessions\n` +
|
|
338
357
|
`/agents \u2014 List / switch agents\n` +
|
|
358
|
+
`/models \u2014 List / switch models\n` +
|
|
339
359
|
`/status \u2014 Bot status\n` +
|
|
340
360
|
`/host \u2014 Host machine info\n` +
|
|
341
361
|
`/switch \u2014 Switch working directory\n` +
|
|
@@ -454,6 +474,22 @@ export class TelegramBot extends Bot {
|
|
|
454
474
|
}
|
|
455
475
|
await ctx.reply(lines.join('\n'), { parseMode: 'HTML', keyboard: { inline_keyboard: rows } });
|
|
456
476
|
}
|
|
477
|
+
async cmdModels(ctx) {
|
|
478
|
+
const cs = this.chat(ctx.chatId);
|
|
479
|
+
const res = this.fetchModels(cs.agent);
|
|
480
|
+
const currentModel = this.modelForAgent(cs.agent);
|
|
481
|
+
const lines = [`<b>Models for ${escapeHtml(cs.agent)}</b>\n`];
|
|
482
|
+
const rows = [];
|
|
483
|
+
for (const m of res.models) {
|
|
484
|
+
const isCurrent = m.id === currentModel || m.alias === currentModel;
|
|
485
|
+
const status = isCurrent ? '\u25CF' : '\u25CB';
|
|
486
|
+
const display = m.alias ? `${m.alias} (${m.id})` : m.id;
|
|
487
|
+
lines.push(`${status} <code>${escapeHtml(display)}</code>${isCurrent ? ' \u2190 current' : ''}`);
|
|
488
|
+
const label = isCurrent ? `\u25CF ${m.alias || m.id}` : (m.alias || m.id);
|
|
489
|
+
rows.push([{ text: label, callback_data: `mod:${m.id}` }]);
|
|
490
|
+
}
|
|
491
|
+
await ctx.reply(lines.join('\n'), { parseMode: 'HTML', keyboard: { inline_keyboard: rows } });
|
|
492
|
+
}
|
|
457
493
|
async cmdRestart(ctx) {
|
|
458
494
|
const activeTasks = this.activeTasks.size;
|
|
459
495
|
if (activeTasks > 0) {
|
|
@@ -598,6 +634,12 @@ export class TelegramBot extends Bot {
|
|
|
598
634
|
tp.push(`cached: ${fmtTokens(result.cachedInputTokens)}`);
|
|
599
635
|
if (result.outputTokens != null)
|
|
600
636
|
tp.push(`out: ${fmtTokens(result.outputTokens)}`);
|
|
637
|
+
// Context window usage percentage (based on input tokens = full conversation context)
|
|
638
|
+
const ctxMax = getContextWindowSize(result.model);
|
|
639
|
+
if (ctxMax && result.inputTokens != null) {
|
|
640
|
+
const pct = (result.inputTokens / ctxMax * 100).toFixed(1);
|
|
641
|
+
tp.push(`ctx: ${pct}%`);
|
|
642
|
+
}
|
|
601
643
|
tokenBlock = `\n<blockquote expandable>${tp.join(' ')}</blockquote>`;
|
|
602
644
|
}
|
|
603
645
|
const quickReplies = result.incomplete ? [] : detectQuickReplies(result.message);
|
|
@@ -760,6 +802,21 @@ export class TelegramBot extends Bot {
|
|
|
760
802
|
await ctx.editReply(ctx.messageId, `<b>Switched to ${escapeHtml(agent)}</b>\n\nSession has been reset. Previous conversation history will not carry over.\nSend a message to start a new conversation.`, { parseMode: 'HTML' });
|
|
761
803
|
return;
|
|
762
804
|
}
|
|
805
|
+
if (data.startsWith('mod:')) {
|
|
806
|
+
const modelId = data.slice(4);
|
|
807
|
+
const cs = this.chat(ctx.chatId);
|
|
808
|
+
const currentModel = this.modelForAgent(cs.agent);
|
|
809
|
+
if (currentModel === modelId) {
|
|
810
|
+
await ctx.answerCallback(`Already using ${modelId}`);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
this.setModelForAgent(cs.agent, modelId);
|
|
814
|
+
cs.sessionId = null;
|
|
815
|
+
this.log(`model switched to ${modelId} for ${cs.agent} chat=${ctx.chatId}`);
|
|
816
|
+
await ctx.answerCallback(`Switched to ${modelId}`);
|
|
817
|
+
await ctx.editReply(ctx.messageId, `<b>Model switched to <code>${escapeHtml(modelId)}</code></b>\n\nAgent: ${escapeHtml(cs.agent)}\nSession has been reset. Send a message to start a new conversation.`, { parseMode: 'HTML' });
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
763
820
|
if (data.startsWith('qr:')) {
|
|
764
821
|
const parts = data.split(':');
|
|
765
822
|
if (parts.length === 3) {
|
|
@@ -788,6 +845,9 @@ export class TelegramBot extends Bot {
|
|
|
788
845
|
case 'agents':
|
|
789
846
|
await this.cmdAgents(ctx);
|
|
790
847
|
return;
|
|
848
|
+
case 'models':
|
|
849
|
+
await this.cmdModels(ctx);
|
|
850
|
+
return;
|
|
791
851
|
case 'status':
|
|
792
852
|
await this.cmdStatus(ctx);
|
|
793
853
|
return;
|
package/dist/bot.js
CHANGED
|
@@ -7,8 +7,8 @@ import os from 'node:os';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { execSync, spawn } from 'node:child_process';
|
|
10
|
-
import { doStream, getSessions, getUsage, listAgents, } from './code-agent.js';
|
|
11
|
-
export const VERSION = '0.2.
|
|
10
|
+
import { doStream, getSessions, getUsage, listAgents, listModels, } from './code-agent.js';
|
|
11
|
+
export const VERSION = '0.2.8';
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Helpers
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
@@ -186,6 +186,16 @@ export class Bot {
|
|
|
186
186
|
fetchAgents() {
|
|
187
187
|
return listAgents();
|
|
188
188
|
}
|
|
189
|
+
fetchModels(agent) {
|
|
190
|
+
return listModels(agent);
|
|
191
|
+
}
|
|
192
|
+
setModelForAgent(agent, modelId) {
|
|
193
|
+
if (agent === 'codex')
|
|
194
|
+
this.codexModel = modelId;
|
|
195
|
+
else
|
|
196
|
+
this.claudeModel = modelId;
|
|
197
|
+
this.log(`model for ${agent} changed to ${modelId}`);
|
|
198
|
+
}
|
|
189
199
|
getStatusData(chatId) {
|
|
190
200
|
const cs = this.chat(chatId);
|
|
191
201
|
const mem = process.memoryUsage();
|
package/dist/code-agent.js
CHANGED
|
@@ -528,6 +528,21 @@ export function listAgents() {
|
|
|
528
528
|
],
|
|
529
529
|
};
|
|
530
530
|
}
|
|
531
|
+
const CLAUDE_MODELS = [
|
|
532
|
+
{ id: 'claude-opus-4-6', alias: 'opus' },
|
|
533
|
+
{ id: 'claude-sonnet-4-5-20250929', alias: 'sonnet' },
|
|
534
|
+
{ id: 'claude-haiku-4-5-20250929', alias: 'haiku' },
|
|
535
|
+
];
|
|
536
|
+
const CODEX_MODELS = [
|
|
537
|
+
{ id: 'o4-mini', alias: null },
|
|
538
|
+
{ id: 'o3', alias: null },
|
|
539
|
+
{ id: 'gpt-5.4', alias: null },
|
|
540
|
+
{ id: 'gpt-4.1', alias: null },
|
|
541
|
+
{ id: 'codex-mini', alias: null },
|
|
542
|
+
];
|
|
543
|
+
export function listModels(agent) {
|
|
544
|
+
return { agent, models: agent === 'codex' ? CODEX_MODELS : CLAUDE_MODELS };
|
|
545
|
+
}
|
|
531
546
|
function toIsoFromEpochSeconds(value) {
|
|
532
547
|
const n = Number(value);
|
|
533
548
|
if (!Number.isFinite(n) || n <= 0)
|
|
@@ -728,6 +743,92 @@ function getCodexUsageFromSessions(home) {
|
|
|
728
743
|
}
|
|
729
744
|
return null;
|
|
730
745
|
}
|
|
746
|
+
// ---------------------------------------------------------------------------
|
|
747
|
+
// Claude usage from OAuth API (https://api.anthropic.com/api/oauth/usage)
|
|
748
|
+
// ---------------------------------------------------------------------------
|
|
749
|
+
function getClaudeOAuthToken() {
|
|
750
|
+
try {
|
|
751
|
+
const raw = execSync('security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null', {
|
|
752
|
+
encoding: 'utf-8', timeout: 3000,
|
|
753
|
+
}).trim();
|
|
754
|
+
if (!raw)
|
|
755
|
+
return null;
|
|
756
|
+
const parsed = JSON.parse(raw);
|
|
757
|
+
return parsed?.claudeAiOauth?.accessToken || null;
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function getClaudeUsageFromOAuth() {
|
|
764
|
+
const token = getClaudeOAuthToken();
|
|
765
|
+
if (!token)
|
|
766
|
+
return null;
|
|
767
|
+
try {
|
|
768
|
+
const raw = execSync(`curl -s --max-time 5 -H "Authorization: Bearer ${token}" -H "anthropic-beta: oauth-2025-04-20" -H "Content-Type: application/json" "https://api.anthropic.com/api/oauth/usage"`, { encoding: 'utf-8', timeout: 8000 }).trim();
|
|
769
|
+
if (!raw || raw[0] !== '{')
|
|
770
|
+
return null;
|
|
771
|
+
const data = JSON.parse(raw);
|
|
772
|
+
const capturedAt = new Date().toISOString();
|
|
773
|
+
const makeWindow = (label, entry) => {
|
|
774
|
+
if (!entry || typeof entry !== 'object')
|
|
775
|
+
return null;
|
|
776
|
+
const usedPercent = roundPercent(entry.utilization);
|
|
777
|
+
if (usedPercent == null)
|
|
778
|
+
return null;
|
|
779
|
+
const remainingPercent = Math.max(0, Math.round((100 - usedPercent) * 10) / 10);
|
|
780
|
+
const resetAt = typeof entry.resets_at === 'string' ? entry.resets_at : null;
|
|
781
|
+
let resetAfterSeconds = null;
|
|
782
|
+
if (resetAt) {
|
|
783
|
+
const resetAtMs = Date.parse(resetAt);
|
|
784
|
+
if (Number.isFinite(resetAtMs)) {
|
|
785
|
+
resetAfterSeconds = Math.max(0, Math.round((resetAtMs - Date.now()) / 1000));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return {
|
|
789
|
+
label,
|
|
790
|
+
usedPercent,
|
|
791
|
+
remainingPercent,
|
|
792
|
+
resetAt,
|
|
793
|
+
resetAfterSeconds,
|
|
794
|
+
status: usedPercent >= 100 ? 'limit_reached' : usedPercent >= 80 ? 'warning' : 'allowed',
|
|
795
|
+
};
|
|
796
|
+
};
|
|
797
|
+
const windows = [];
|
|
798
|
+
const w5h = makeWindow('5h', data.five_hour);
|
|
799
|
+
if (w5h)
|
|
800
|
+
windows.push(w5h);
|
|
801
|
+
const w7d = makeWindow('7d', data.seven_day);
|
|
802
|
+
if (w7d)
|
|
803
|
+
windows.push(w7d);
|
|
804
|
+
const w7dOpus = makeWindow('7d Opus', data.seven_day_opus);
|
|
805
|
+
if (w7dOpus)
|
|
806
|
+
windows.push(w7dOpus);
|
|
807
|
+
const w7dSonnet = makeWindow('7d Sonnet', data.seven_day_sonnet);
|
|
808
|
+
if (w7dSonnet)
|
|
809
|
+
windows.push(w7dSonnet);
|
|
810
|
+
const wExtra = makeWindow('Extra', data.extra_usage);
|
|
811
|
+
if (wExtra)
|
|
812
|
+
windows.push(wExtra);
|
|
813
|
+
if (!windows.length)
|
|
814
|
+
return null;
|
|
815
|
+
const overallStatus = windows.some(w => w.status === 'limit_reached') ? 'limit_reached'
|
|
816
|
+
: windows.some(w => w.status === 'warning') ? 'warning'
|
|
817
|
+
: 'allowed';
|
|
818
|
+
return {
|
|
819
|
+
ok: true,
|
|
820
|
+
agent: 'claude',
|
|
821
|
+
source: 'oauth-api',
|
|
822
|
+
capturedAt,
|
|
823
|
+
status: overallStatus,
|
|
824
|
+
windows,
|
|
825
|
+
error: null,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
731
832
|
function getClaudeUsageFromTelemetry(home, model) {
|
|
732
833
|
const telemetryRoot = path.join(home, '.claude', 'telemetry');
|
|
733
834
|
if (!fs.existsSync(telemetryRoot))
|
|
@@ -821,6 +922,7 @@ export function getUsage(opts) {
|
|
|
821
922
|
|| getCodexUsageFromSessions(home)
|
|
822
923
|
|| emptyUsage('codex', 'No recent Codex usage data found.');
|
|
823
924
|
}
|
|
824
|
-
return
|
|
925
|
+
return getClaudeUsageFromOAuth()
|
|
926
|
+
|| getClaudeUsageFromTelemetry(home, opts.model)
|
|
825
927
|
|| emptyUsage('claude', 'No recent Claude usage data found.');
|
|
826
928
|
}
|