beast-agent 1.8.0 → 2.0.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/LICENSE +21 -21
- package/README.md +130 -128
- package/bin/beast-agent.js +137 -137
- package/package.json +1 -1
- package/scripts/fix-electron.js +138 -138
- package/scripts/release.js +137 -131
- package/scripts/swap-electron.js +40 -40
- package/src/agent/agentdefs.js +122 -122
- package/src/agent/bots.js +580 -580
- package/src/agent/bus.js +389 -389
- package/src/agent/computeruse.js +200 -200
- package/src/agent/config.js +284 -284
- package/src/agent/discord.js +332 -332
- package/src/agent/engine.js +85 -11
- package/src/agent/kb.js +123 -123
- package/src/agent/llm.js +430 -430
- package/src/agent/logger.js +90 -90
- package/src/agent/mcp.js +427 -0
- package/src/agent/mem0.js +605 -605
- package/src/agent/memory.js +427 -427
- package/src/agent/mqueue.js +124 -124
- package/src/agent/pdf.js +20 -20
- package/src/agent/research.js +133 -133
- package/src/agent/scripts/news.py +113 -113
- package/src/agent/scripts/stealthsearch.py +30 -30
- package/src/agent/scripts/websearch.py +225 -225
- package/src/agent/searxng.js +325 -325
- package/src/agent/seeds/brainstorming/SKILL.md +90 -90
- package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
- package/src/agent/seeds/executing-plans/SKILL.md +60 -60
- package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
- package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
- package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
- package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
- package/src/agent/seeds/writing-plans/SKILL.md +162 -162
- package/src/agent/seeds/writing-skills/SKILL.md +229 -229
- package/src/agent/skills.js +652 -651
- package/src/agent/store.js +378 -378
- package/src/agent/telegram.js +155 -155
- package/src/agent/tokens.js +39 -39
- package/src/agent/usage.js +125 -125
- package/src/agent/watchers.js +312 -312
- package/src/agent/watext.js +80 -80
- package/src/agent/whatsapp.js +555 -555
- package/src/cron.js +255 -255
- package/src/main.js +385 -6
- package/src/preload.js +13 -0
- package/src/renderer/browserPreload.js +73 -73
- package/src/renderer/i18n.js +38 -0
- package/src/renderer/index.html +448 -407
- package/src/renderer/renderer.js +897 -42
- package/src/renderer/style.css +275 -11
package/src/agent/telegram.js
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* TELEGRAM KÖPRÜSÜ (FEATURE 3)
|
|
4
|
-
WhatsApp köprüsünün Telegram hali — bağımlılık yok (saf Node https):
|
|
5
|
-
- Bot API long polling (getUpdates) ile gelen mesajlar onIncoming'e düşer
|
|
6
|
-
- send(chatId, text) cevap döner; 4096 karakter sınırında bölerek gönderir
|
|
7
|
-
- Aynı allow list mantığı: main tarafındaki tgFind() listesindeki kişilere cevap verir */
|
|
8
|
-
|
|
9
|
-
const https = require('https');
|
|
10
|
-
|
|
11
|
-
const API_BASE = 'https://api.telegram.org/bot';
|
|
12
|
-
const SEND_CHUNK = 3800; // Telegram mesaj sınırı 4096 — güvenli pay
|
|
13
|
-
|
|
14
|
-
class TelegramBridge {
|
|
15
|
-
constructor({ token, emit, onIncoming }) {
|
|
16
|
-
this.token = String(token || '').trim();
|
|
17
|
-
this.emit = emit || (() => {});
|
|
18
|
-
this.onIncoming = onIncoming || null;
|
|
19
|
-
this.connected = false;
|
|
20
|
-
this.stopping = false;
|
|
21
|
-
this.status = 'disconnected';
|
|
22
|
-
this.offset = 0;
|
|
23
|
-
this.me = null;
|
|
24
|
-
this._req = null; // aktif long-poll isteği (iptal için)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
_setStatus(status, user) {
|
|
28
|
-
this.status = status;
|
|
29
|
-
this.connected = status === 'connected';
|
|
30
|
-
this.emit({ type: 'status', status, user: user || null });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/* Bot API çağrısı — JSON POST, promise sarmalı */
|
|
34
|
-
api(method, body = {}, opts = {}) {
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
36
|
-
const payload = JSON.stringify(body);
|
|
37
|
-
const req = https.request(
|
|
38
|
-
`${API_BASE}${this.token}/${method}`,
|
|
39
|
-
{
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
42
|
-
timeout: opts.timeout || 15000,
|
|
43
|
-
},
|
|
44
|
-
(res) => {
|
|
45
|
-
let data = '';
|
|
46
|
-
res.setEncoding('utf8');
|
|
47
|
-
res.on('data', (c) => { data += c; });
|
|
48
|
-
res.on('end', () => {
|
|
49
|
-
try {
|
|
50
|
-
const j = JSON.parse(data || '{}');
|
|
51
|
-
if (j.ok) resolve(j.result);
|
|
52
|
-
else reject(new Error(`telegram ${method}: ${j.description || 'hata ' + res.statusCode}`));
|
|
53
|
-
} catch (e) {
|
|
54
|
-
reject(new Error(`telegram ${method}: bozuk yanıt`));
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
|
|
60
|
-
req.on('error', reject);
|
|
61
|
-
this._req = req;
|
|
62
|
-
req.write(payload);
|
|
63
|
-
req.end();
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async start() {
|
|
68
|
-
if (!this.token) {
|
|
69
|
-
this._setStatus('error');
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
this.stopping = false;
|
|
73
|
-
this.offset = 0;
|
|
74
|
-
this._setStatus('connecting');
|
|
75
|
-
try {
|
|
76
|
-
const me = await this.api('getMe', {});
|
|
77
|
-
this.me = me;
|
|
78
|
-
this._setStatus('connected', '@' + (me.username || me.first_name || 'bot'));
|
|
79
|
-
} catch (e) {
|
|
80
|
-
this._setStatus('error');
|
|
81
|
-
throw e;
|
|
82
|
-
}
|
|
83
|
-
this._pollLoop().catch(() => {});
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/* long polling döngüsü — bağlantı koparsa 3 sn bekleyip devam */
|
|
88
|
-
async _pollLoop() {
|
|
89
|
-
while (!this.stopping) {
|
|
90
|
-
try {
|
|
91
|
-
const updates = await this.api(
|
|
92
|
-
'getUpdates',
|
|
93
|
-
{ timeout: 25, offset: this.offset, allowed_updates: ['message'] },
|
|
94
|
-
{ timeout: 35000 }
|
|
95
|
-
);
|
|
96
|
-
if (this.stopping) break;
|
|
97
|
-
for (const u of Array.isArray(updates) ? updates : []) {
|
|
98
|
-
this.offset = Math.max(this.offset, (u.update_id || 0) + 1);
|
|
99
|
-
const msg = u.message;
|
|
100
|
-
if (!msg || !msg.text || !msg.from || msg.from.is_bot) continue;
|
|
101
|
-
const chatId = msg.chat && msg.chat.id;
|
|
102
|
-
if (chatId === undefined || chatId === null) continue;
|
|
103
|
-
const payload = {
|
|
104
|
-
text: String(msg.text).slice(0, 6000),
|
|
105
|
-
senderId: String(msg.from.id || ''),
|
|
106
|
-
username: String(msg.from.username || ''),
|
|
107
|
-
senderName: String(msg.from.first_name || msg.from.username || ''),
|
|
108
|
-
isGroup: !!(msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')),
|
|
109
|
-
};
|
|
110
|
-
try {
|
|
111
|
-
if (this.onIncoming) this.onIncoming(String(chatId), payload);
|
|
112
|
-
} catch {}
|
|
113
|
-
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
if (this.stopping) break;
|
|
116
|
-
this.emit({ type: 'poll-error', error: String((e && e.message) || e) });
|
|
117
|
-
await new Promise((r) => setTimeout(r, 3000));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async stop() {
|
|
123
|
-
this.stopping = true;
|
|
124
|
-
try { if (this._req) this._req.destroy(new Error('stop')); } catch {}
|
|
125
|
-
this._req = null;
|
|
126
|
-
this.connected = false;
|
|
127
|
-
this.status = 'disconnected';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
snapshot() {
|
|
131
|
-
return {
|
|
132
|
-
status: this.status,
|
|
133
|
-
user: this.me ? '@' + (this.me.username || this.me.first_name || 'bot') : null,
|
|
134
|
-
connected: this.connected,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/* Metin gönder — 4096 sınırı için parçalara böl */
|
|
139
|
-
async send(chatId, text) {
|
|
140
|
-
const t = String(text || '');
|
|
141
|
-
if (!t.trim()) return false;
|
|
142
|
-
const chunks = [];
|
|
143
|
-
for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
|
|
144
|
-
for (const part of chunks) {
|
|
145
|
-
await this.api('sendMessage', {
|
|
146
|
-
chat_id: chatId,
|
|
147
|
-
text: part,
|
|
148
|
-
disable_web_page_preview: true,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
module.exports = { TelegramBridge };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* TELEGRAM KÖPRÜSÜ (FEATURE 3)
|
|
4
|
+
WhatsApp köprüsünün Telegram hali — bağımlılık yok (saf Node https):
|
|
5
|
+
- Bot API long polling (getUpdates) ile gelen mesajlar onIncoming'e düşer
|
|
6
|
+
- send(chatId, text) cevap döner; 4096 karakter sınırında bölerek gönderir
|
|
7
|
+
- Aynı allow list mantığı: main tarafındaki tgFind() listesindeki kişilere cevap verir */
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
|
|
11
|
+
const API_BASE = 'https://api.telegram.org/bot';
|
|
12
|
+
const SEND_CHUNK = 3800; // Telegram mesaj sınırı 4096 — güvenli pay
|
|
13
|
+
|
|
14
|
+
class TelegramBridge {
|
|
15
|
+
constructor({ token, emit, onIncoming }) {
|
|
16
|
+
this.token = String(token || '').trim();
|
|
17
|
+
this.emit = emit || (() => {});
|
|
18
|
+
this.onIncoming = onIncoming || null;
|
|
19
|
+
this.connected = false;
|
|
20
|
+
this.stopping = false;
|
|
21
|
+
this.status = 'disconnected';
|
|
22
|
+
this.offset = 0;
|
|
23
|
+
this.me = null;
|
|
24
|
+
this._req = null; // aktif long-poll isteği (iptal için)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_setStatus(status, user) {
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.connected = status === 'connected';
|
|
30
|
+
this.emit({ type: 'status', status, user: user || null });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Bot API çağrısı — JSON POST, promise sarmalı */
|
|
34
|
+
api(method, body = {}, opts = {}) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const payload = JSON.stringify(body);
|
|
37
|
+
const req = https.request(
|
|
38
|
+
`${API_BASE}${this.token}/${method}`,
|
|
39
|
+
{
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
42
|
+
timeout: opts.timeout || 15000,
|
|
43
|
+
},
|
|
44
|
+
(res) => {
|
|
45
|
+
let data = '';
|
|
46
|
+
res.setEncoding('utf8');
|
|
47
|
+
res.on('data', (c) => { data += c; });
|
|
48
|
+
res.on('end', () => {
|
|
49
|
+
try {
|
|
50
|
+
const j = JSON.parse(data || '{}');
|
|
51
|
+
if (j.ok) resolve(j.result);
|
|
52
|
+
else reject(new Error(`telegram ${method}: ${j.description || 'hata ' + res.statusCode}`));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
reject(new Error(`telegram ${method}: bozuk yanıt`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
this._req = req;
|
|
62
|
+
req.write(payload);
|
|
63
|
+
req.end();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async start() {
|
|
68
|
+
if (!this.token) {
|
|
69
|
+
this._setStatus('error');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
this.stopping = false;
|
|
73
|
+
this.offset = 0;
|
|
74
|
+
this._setStatus('connecting');
|
|
75
|
+
try {
|
|
76
|
+
const me = await this.api('getMe', {});
|
|
77
|
+
this.me = me;
|
|
78
|
+
this._setStatus('connected', '@' + (me.username || me.first_name || 'bot'));
|
|
79
|
+
} catch (e) {
|
|
80
|
+
this._setStatus('error');
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
this._pollLoop().catch(() => {});
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* long polling döngüsü — bağlantı koparsa 3 sn bekleyip devam */
|
|
88
|
+
async _pollLoop() {
|
|
89
|
+
while (!this.stopping) {
|
|
90
|
+
try {
|
|
91
|
+
const updates = await this.api(
|
|
92
|
+
'getUpdates',
|
|
93
|
+
{ timeout: 25, offset: this.offset, allowed_updates: ['message'] },
|
|
94
|
+
{ timeout: 35000 }
|
|
95
|
+
);
|
|
96
|
+
if (this.stopping) break;
|
|
97
|
+
for (const u of Array.isArray(updates) ? updates : []) {
|
|
98
|
+
this.offset = Math.max(this.offset, (u.update_id || 0) + 1);
|
|
99
|
+
const msg = u.message;
|
|
100
|
+
if (!msg || !msg.text || !msg.from || msg.from.is_bot) continue;
|
|
101
|
+
const chatId = msg.chat && msg.chat.id;
|
|
102
|
+
if (chatId === undefined || chatId === null) continue;
|
|
103
|
+
const payload = {
|
|
104
|
+
text: String(msg.text).slice(0, 6000),
|
|
105
|
+
senderId: String(msg.from.id || ''),
|
|
106
|
+
username: String(msg.from.username || ''),
|
|
107
|
+
senderName: String(msg.from.first_name || msg.from.username || ''),
|
|
108
|
+
isGroup: !!(msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')),
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
if (this.onIncoming) this.onIncoming(String(chatId), payload);
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (this.stopping) break;
|
|
116
|
+
this.emit({ type: 'poll-error', error: String((e && e.message) || e) });
|
|
117
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async stop() {
|
|
123
|
+
this.stopping = true;
|
|
124
|
+
try { if (this._req) this._req.destroy(new Error('stop')); } catch {}
|
|
125
|
+
this._req = null;
|
|
126
|
+
this.connected = false;
|
|
127
|
+
this.status = 'disconnected';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
snapshot() {
|
|
131
|
+
return {
|
|
132
|
+
status: this.status,
|
|
133
|
+
user: this.me ? '@' + (this.me.username || this.me.first_name || 'bot') : null,
|
|
134
|
+
connected: this.connected,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Metin gönder — 4096 sınırı için parçalara böl */
|
|
139
|
+
async send(chatId, text) {
|
|
140
|
+
const t = String(text || '');
|
|
141
|
+
if (!t.trim()) return false;
|
|
142
|
+
const chunks = [];
|
|
143
|
+
for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
|
|
144
|
+
for (const part of chunks) {
|
|
145
|
+
await this.api('sendMessage', {
|
|
146
|
+
chat_id: chatId,
|
|
147
|
+
text: part,
|
|
148
|
+
disable_web_page_preview: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { TelegramBridge };
|
package/src/agent/tokens.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast tokens: dependency-free token estimation for context budgeting.
|
|
4
|
-
Accuracy is calibrated at runtime with real usage.prompt_tokens values
|
|
5
|
-
fed back from the API (see Engine.tokRatio). */
|
|
6
|
-
|
|
7
|
-
function estTokens(text) {
|
|
8
|
-
const s = String(text || '');
|
|
9
|
-
if (!s) return 0;
|
|
10
|
-
let narrow = 0;
|
|
11
|
-
let wide = 0;
|
|
12
|
-
for (let i = 0; i < s.length; i++) {
|
|
13
|
-
// CJK ve emoji gibi geniş karakterler token başına daha fazla maliyetlidir
|
|
14
|
-
if (s.charCodeAt(i) > 0x2e7f) wide++;
|
|
15
|
-
else narrow++;
|
|
16
|
-
}
|
|
17
|
-
return Math.ceil((narrow + wide * 2.5) / 4);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function estMsgTokens(m) {
|
|
21
|
-
if (!m) return 0;
|
|
22
|
-
let t = 4; // mesaj zarfı (role vb.)
|
|
23
|
-
const content = m.content;
|
|
24
|
-
if (typeof content === 'string') {
|
|
25
|
-
t += estTokens(content);
|
|
26
|
-
} else if (Array.isArray(content)) {
|
|
27
|
-
for (const p of content) {
|
|
28
|
-
if (p && p.type === 'text') t += estTokens(p.text);
|
|
29
|
-
else if (p && p.type === 'image_url') t += 600; // düşük detaylı görsel yaklaşımı
|
|
30
|
-
else t += estTokens(JSON.stringify(p || ''));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (m.tool_calls && m.tool_calls.length) {
|
|
34
|
-
t += estTokens(JSON.stringify(m.tool_calls)) + 3 * m.tool_calls.length;
|
|
35
|
-
}
|
|
36
|
-
return t;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = { estTokens, estMsgTokens };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast tokens: dependency-free token estimation for context budgeting.
|
|
4
|
+
Accuracy is calibrated at runtime with real usage.prompt_tokens values
|
|
5
|
+
fed back from the API (see Engine.tokRatio). */
|
|
6
|
+
|
|
7
|
+
function estTokens(text) {
|
|
8
|
+
const s = String(text || '');
|
|
9
|
+
if (!s) return 0;
|
|
10
|
+
let narrow = 0;
|
|
11
|
+
let wide = 0;
|
|
12
|
+
for (let i = 0; i < s.length; i++) {
|
|
13
|
+
// CJK ve emoji gibi geniş karakterler token başına daha fazla maliyetlidir
|
|
14
|
+
if (s.charCodeAt(i) > 0x2e7f) wide++;
|
|
15
|
+
else narrow++;
|
|
16
|
+
}
|
|
17
|
+
return Math.ceil((narrow + wide * 2.5) / 4);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function estMsgTokens(m) {
|
|
21
|
+
if (!m) return 0;
|
|
22
|
+
let t = 4; // mesaj zarfı (role vb.)
|
|
23
|
+
const content = m.content;
|
|
24
|
+
if (typeof content === 'string') {
|
|
25
|
+
t += estTokens(content);
|
|
26
|
+
} else if (Array.isArray(content)) {
|
|
27
|
+
for (const p of content) {
|
|
28
|
+
if (p && p.type === 'text') t += estTokens(p.text);
|
|
29
|
+
else if (p && p.type === 'image_url') t += 600; // düşük detaylı görsel yaklaşımı
|
|
30
|
+
else t += estTokens(JSON.stringify(p || ''));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (m.tool_calls && m.tool_calls.length) {
|
|
34
|
+
t += estTokens(JSON.stringify(m.tool_calls)) + 3 * m.tool_calls.length;
|
|
35
|
+
}
|
|
36
|
+
return t;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { estTokens, estMsgTokens };
|
package/src/agent/usage.js
CHANGED
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast kullanım sayacı: model çağrılarının token/çağrı/maliyet toplamları.
|
|
4
|
-
Gün bazlı tutulur (%APPDATA%\beast\usage.json), ~70 gün saklanır.
|
|
5
|
-
Maliyet USD: config.yaml providers.price_in / price_out (1M token başına)
|
|
6
|
-
verildiyse hesaplanır; verilmiyorsa 0 kalır (token raporu yine doğru). */
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const { beastRoot } = require('./memory');
|
|
11
|
-
|
|
12
|
-
function file() {
|
|
13
|
-
return path.join(beastRoot(), 'usage.json');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let days = []; // [{ date:'YYYY-MM-DD', models:{ 'pid::model': {calls,pin,pout,cost} } }]
|
|
17
|
-
let loaded = false;
|
|
18
|
-
|
|
19
|
-
function today() {
|
|
20
|
-
const d = new Date();
|
|
21
|
-
const p = (n) => String(n).padStart(2, '0');
|
|
22
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function load() {
|
|
26
|
-
if (loaded) return;
|
|
27
|
-
loaded = true;
|
|
28
|
-
try {
|
|
29
|
-
const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
30
|
-
days = Array.isArray(raw.days) ? raw.days.slice(-70) : [];
|
|
31
|
-
} catch {
|
|
32
|
-
days = [];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function save() {
|
|
37
|
-
try {
|
|
38
|
-
fs.mkdirSync(beastRoot(), { recursive: true });
|
|
39
|
-
const tmp = file() + '.tmp';
|
|
40
|
-
fs.writeFileSync(tmp, JSON.stringify({ days }, null, 2));
|
|
41
|
-
fs.renameSync(tmp, file());
|
|
42
|
-
} catch {}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function dayRow(date) {
|
|
46
|
-
let row = days.find((d) => d.date === date);
|
|
47
|
-
if (!row) {
|
|
48
|
-
row = { date, models: {} };
|
|
49
|
-
days.push(row);
|
|
50
|
-
if (days.length > 70) days = days.slice(-70);
|
|
51
|
-
}
|
|
52
|
-
return row;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function keyOf(providerId, model) {
|
|
56
|
-
return `${providerId || '?'}::${model || '?'}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/* Bir çağrının kullanımını işle. meta: {providerId, model, costIn, costOut}
|
|
60
|
-
(1M token fiyatları, opsiyonel). Saf davranış + disk. */
|
|
61
|
-
function record({ providerId, model, promptTokens = 0, completionTokens = 0, costIn = null, costOut = null } = {}) {
|
|
62
|
-
load();
|
|
63
|
-
const pin = Math.max(0, Math.round(Number(promptTokens) || 0));
|
|
64
|
-
const pout = Math.max(0, Math.round(Number(completionTokens) || 0));
|
|
65
|
-
if (!pin && !pout) return;
|
|
66
|
-
const row = dayRow(today());
|
|
67
|
-
const k = keyOf(providerId, model);
|
|
68
|
-
const m = row.models[k] || (row.models[k] = { calls: 0, pin: 0, pout: 0, cost: 0 });
|
|
69
|
-
m.calls += 1;
|
|
70
|
-
m.pin += pin;
|
|
71
|
-
m.pout += pout;
|
|
72
|
-
if (Number.isFinite(costIn) && Number.isFinite(costOut)) {
|
|
73
|
-
m.cost += (pin * costIn + pout * costOut) / 1000000;
|
|
74
|
-
}
|
|
75
|
-
save();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function sumRows(rows) {
|
|
79
|
-
const out = {};
|
|
80
|
-
for (const r of rows) {
|
|
81
|
-
for (const [k, v] of Object.entries(r.models || {})) {
|
|
82
|
-
const t = out[k] || (out[k] = { calls: 0, pin: 0, pout: 0, cost: 0 });
|
|
83
|
-
t.calls += v.calls || 0;
|
|
84
|
-
t.pin += v.pin || 0;
|
|
85
|
-
t.pout += v.pout || 0;
|
|
86
|
-
t.cost += v.cost || 0;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function monthPrefix() {
|
|
93
|
-
return today().slice(0, 7);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function report() {
|
|
97
|
-
load();
|
|
98
|
-
const t = today();
|
|
99
|
-
const mp = monthPrefix();
|
|
100
|
-
const todayModels = sumRows(days.filter((d) => d.date === t));
|
|
101
|
-
const monthModels = sumRows(days.filter((d) => String(d.date || '').startsWith(mp)));
|
|
102
|
-
const flatten = (models) =>
|
|
103
|
-
Object.entries(models)
|
|
104
|
-
.map(([k, v]) => ({ model: k, ...v }))
|
|
105
|
-
.sort((a, b) => b.calls - a.calls);
|
|
106
|
-
return {
|
|
107
|
-
today: { date: t, total: totalOf(todayModels), models: flatten(todayModels) },
|
|
108
|
-
month: { prefix: mp, total: totalOf(monthModels), models: flatten(monthModels) },
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function totalOf(models) {
|
|
113
|
-
let calls = 0, pin = 0, pout = 0, cost = 0;
|
|
114
|
-
for (const v of Object.values(models)) {
|
|
115
|
-
calls += v.calls; pin += v.pin; pout += v.pout; cost += v.cost;
|
|
116
|
-
}
|
|
117
|
-
return { calls, pin, pout, cost: Math.round(cost * 10000) / 10000 };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function reset() {
|
|
121
|
-
days = [];
|
|
122
|
-
save();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
module.exports = { record, report, reset, sumRows };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast kullanım sayacı: model çağrılarının token/çağrı/maliyet toplamları.
|
|
4
|
+
Gün bazlı tutulur (%APPDATA%\beast\usage.json), ~70 gün saklanır.
|
|
5
|
+
Maliyet USD: config.yaml providers.price_in / price_out (1M token başına)
|
|
6
|
+
verildiyse hesaplanır; verilmiyorsa 0 kalır (token raporu yine doğru). */
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { beastRoot } = require('./memory');
|
|
11
|
+
|
|
12
|
+
function file() {
|
|
13
|
+
return path.join(beastRoot(), 'usage.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let days = []; // [{ date:'YYYY-MM-DD', models:{ 'pid::model': {calls,pin,pout,cost} } }]
|
|
17
|
+
let loaded = false;
|
|
18
|
+
|
|
19
|
+
function today() {
|
|
20
|
+
const d = new Date();
|
|
21
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
22
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function load() {
|
|
26
|
+
if (loaded) return;
|
|
27
|
+
loaded = true;
|
|
28
|
+
try {
|
|
29
|
+
const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
30
|
+
days = Array.isArray(raw.days) ? raw.days.slice(-70) : [];
|
|
31
|
+
} catch {
|
|
32
|
+
days = [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function save() {
|
|
37
|
+
try {
|
|
38
|
+
fs.mkdirSync(beastRoot(), { recursive: true });
|
|
39
|
+
const tmp = file() + '.tmp';
|
|
40
|
+
fs.writeFileSync(tmp, JSON.stringify({ days }, null, 2));
|
|
41
|
+
fs.renameSync(tmp, file());
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function dayRow(date) {
|
|
46
|
+
let row = days.find((d) => d.date === date);
|
|
47
|
+
if (!row) {
|
|
48
|
+
row = { date, models: {} };
|
|
49
|
+
days.push(row);
|
|
50
|
+
if (days.length > 70) days = days.slice(-70);
|
|
51
|
+
}
|
|
52
|
+
return row;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function keyOf(providerId, model) {
|
|
56
|
+
return `${providerId || '?'}::${model || '?'}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Bir çağrının kullanımını işle. meta: {providerId, model, costIn, costOut}
|
|
60
|
+
(1M token fiyatları, opsiyonel). Saf davranış + disk. */
|
|
61
|
+
function record({ providerId, model, promptTokens = 0, completionTokens = 0, costIn = null, costOut = null } = {}) {
|
|
62
|
+
load();
|
|
63
|
+
const pin = Math.max(0, Math.round(Number(promptTokens) || 0));
|
|
64
|
+
const pout = Math.max(0, Math.round(Number(completionTokens) || 0));
|
|
65
|
+
if (!pin && !pout) return;
|
|
66
|
+
const row = dayRow(today());
|
|
67
|
+
const k = keyOf(providerId, model);
|
|
68
|
+
const m = row.models[k] || (row.models[k] = { calls: 0, pin: 0, pout: 0, cost: 0 });
|
|
69
|
+
m.calls += 1;
|
|
70
|
+
m.pin += pin;
|
|
71
|
+
m.pout += pout;
|
|
72
|
+
if (Number.isFinite(costIn) && Number.isFinite(costOut)) {
|
|
73
|
+
m.cost += (pin * costIn + pout * costOut) / 1000000;
|
|
74
|
+
}
|
|
75
|
+
save();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sumRows(rows) {
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const r of rows) {
|
|
81
|
+
for (const [k, v] of Object.entries(r.models || {})) {
|
|
82
|
+
const t = out[k] || (out[k] = { calls: 0, pin: 0, pout: 0, cost: 0 });
|
|
83
|
+
t.calls += v.calls || 0;
|
|
84
|
+
t.pin += v.pin || 0;
|
|
85
|
+
t.pout += v.pout || 0;
|
|
86
|
+
t.cost += v.cost || 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function monthPrefix() {
|
|
93
|
+
return today().slice(0, 7);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function report() {
|
|
97
|
+
load();
|
|
98
|
+
const t = today();
|
|
99
|
+
const mp = monthPrefix();
|
|
100
|
+
const todayModels = sumRows(days.filter((d) => d.date === t));
|
|
101
|
+
const monthModels = sumRows(days.filter((d) => String(d.date || '').startsWith(mp)));
|
|
102
|
+
const flatten = (models) =>
|
|
103
|
+
Object.entries(models)
|
|
104
|
+
.map(([k, v]) => ({ model: k, ...v }))
|
|
105
|
+
.sort((a, b) => b.calls - a.calls);
|
|
106
|
+
return {
|
|
107
|
+
today: { date: t, total: totalOf(todayModels), models: flatten(todayModels) },
|
|
108
|
+
month: { prefix: mp, total: totalOf(monthModels), models: flatten(monthModels) },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function totalOf(models) {
|
|
113
|
+
let calls = 0, pin = 0, pout = 0, cost = 0;
|
|
114
|
+
for (const v of Object.values(models)) {
|
|
115
|
+
calls += v.calls; pin += v.pin; pout += v.pout; cost += v.cost;
|
|
116
|
+
}
|
|
117
|
+
return { calls, pin, pout, cost: Math.round(cost * 10000) / 10000 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function reset() {
|
|
121
|
+
days = [];
|
|
122
|
+
save();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { record, report, reset, sumRows };
|