beast-agent 2.58.1 → 2.58.2
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/package.json +1 -1
- package/src/agent/engine.js +28 -5
- package/src/agent/llm.js +54 -14
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -811,9 +811,13 @@ class Engine {
|
|
|
811
811
|
payload = squeeze.apply(payload, { cacheKey: String(session.id || '') });
|
|
812
812
|
}
|
|
813
813
|
const cands = this._chatCandidates(sel, wasVision);
|
|
814
|
+
/* AYLIK KOTASI DOLAN SAĞLAYICILAR: aynı sağlayıcının diğer modellerini
|
|
815
|
+
denemek boşuna (kota hesap/workspace bazlı) — bu turda atlanır. */
|
|
816
|
+
const quotaOut = new Set();
|
|
814
817
|
let lastErr = null;
|
|
815
818
|
for (let i = 0; i < cands.length; i++) {
|
|
816
819
|
const c = cands[i];
|
|
820
|
+
if (quotaOut.has(String(c.providerId))) continue;
|
|
817
821
|
let retried413 = false;
|
|
818
822
|
try {
|
|
819
823
|
/* provider bazlı girdi limiti: payload aşıyorsa sıkıştırılıp öyle gönderilir.
|
|
@@ -903,6 +907,15 @@ class Engine {
|
|
|
903
907
|
if (e && (e.name === 'AbortError' || (signal && signal.aborted))) throw e;
|
|
904
908
|
lastErr = e;
|
|
905
909
|
const msgStr = String((e && e.message) || '');
|
|
910
|
+
/* AYLIK KOTA: bu sağlayıcı tur boyunca kapalı — kullanıcıya net söyle,
|
|
911
|
+
FALLOUT varsa farklı sağlayıcıya geç */
|
|
912
|
+
if (e && e.usageLimit) {
|
|
913
|
+
quotaOut.add(String(c.providerId));
|
|
914
|
+
emitSafe(this, session.id, {
|
|
915
|
+
type: 'status',
|
|
916
|
+
status: `⛔ ${c.providerName}: aylık kullanım limiti doldu — aynı sağlayıcının diğer modelleri atlanıyor`,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
906
919
|
/* 413 TPM: sağlayıcı gerçek istek boyutunu söylüyor → kalibre et,
|
|
907
920
|
aynı modeli yeniden sıkıştırıp BİR KEZ daha dene */
|
|
908
921
|
const m413 = /Requested\s+([\d,]+)/i.exec(msgStr) || (e && e.status === 413 ? [, 0] : null);
|
|
@@ -925,7 +938,11 @@ class Engine {
|
|
|
925
938
|
([400, 404, 422].includes(e.status) ||
|
|
926
939
|
/\bno endpoints\b/i.test(msgStr) ||
|
|
927
940
|
/\bimage input\b/i.test(msgStr));
|
|
928
|
-
|
|
941
|
+
/* sıradaki aday: kotası dolmuş sağlayıcılar atlanır */
|
|
942
|
+
let next = null;
|
|
943
|
+
for (let j = i + 1; j < cands.length; j++) {
|
|
944
|
+
if (!quotaOut.has(String(cands[j].providerId))) { next = cands[j]; break; }
|
|
945
|
+
}
|
|
929
946
|
if (!next) break; // zincir bitti — hatayı yukarı fırlat
|
|
930
947
|
if (imgIssue) {
|
|
931
948
|
emitSafe(this, session.id, {
|
|
@@ -1417,15 +1434,21 @@ class Engine {
|
|
|
1417
1434
|
files = fs.readdirSync(this.sessionsDir).filter((f) => f.endsWith('.jsonl'));
|
|
1418
1435
|
} catch {}
|
|
1419
1436
|
const out = [];
|
|
1437
|
+
const fin = [];
|
|
1420
1438
|
for (const f of files) {
|
|
1421
1439
|
const id = f.replace(/\.jsonl$/, '');
|
|
1422
1440
|
const v = this._view(this._load(id));
|
|
1423
1441
|
if (v.isBg) continue;
|
|
1424
1442
|
if (v.isBotDm) continue; // botlar arası DM — yalnız admin DM Log ekranında
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1443
|
+
/* FİNANS GEÇMİŞİ KAYBOLMAZ: normal sohbet listesi 100 ile sınırlı olsa da
|
|
1444
|
+
finance sohbetleri AYRICA listelenir — 100+ oturumu olan kullanıcıda
|
|
1445
|
+
eski finance sohbetleri listeden düşüp "kaybolmuş" görünmez. */
|
|
1446
|
+
(v.finance ? fin : out).push(v);
|
|
1447
|
+
}
|
|
1448
|
+
const byNew = (a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt));
|
|
1449
|
+
out.sort(byNew);
|
|
1450
|
+
fin.sort(byNew);
|
|
1451
|
+
return out.slice(0, 100).concat(fin.slice(0, 60)).sort(byNew);
|
|
1429
1452
|
}
|
|
1430
1453
|
|
|
1431
1454
|
/* Panel oturumları: belirli bir bgTitle taşıyan gizli oturumlar (Beast Code,
|
package/src/agent/llm.js
CHANGED
|
@@ -64,7 +64,27 @@ const HINTS = {
|
|
|
64
64
|
504: 'sağlayıcı zaman aşımı',
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
/* AYLIK KOTA / KULLANIM LİMİTİ: 429 gövdesindeki "usage limit / quota" sinyali
|
|
68
|
+
hız limitinden FARKLI — tekrar denemek çözmez, aynı sağlayıcının diğer
|
|
69
|
+
modelleri de kapalıdır. Bu ayrım motorun FALLOUT kararını da besler. */
|
|
70
|
+
function isUsageLimitDetail(detail) {
|
|
71
|
+
return /GoUsageLimit|usage[_ ]?limit|insufficient_quota|monthly limit|kullanım limiti/i.test(
|
|
72
|
+
String(detail || '')
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
function friendlyError(status, statusText, detail) {
|
|
77
|
+
const d = String(detail || '');
|
|
78
|
+
if (status === 429 && isUsageLimitDetail(d)) {
|
|
79
|
+
let msg = 'HTTP 429 — AYLIK KULLANIM LİMİTİ DOLDU (hız limiti değil)';
|
|
80
|
+
const reset = /Resets?\s+in\s+([^."}]+)/i.exec(d);
|
|
81
|
+
if (reset) msg += ` — sıfırlanma: ${reset[1].trim()} sonra`;
|
|
82
|
+
const link = /(https?:\/\/[^\s"}]+)/.exec(d);
|
|
83
|
+
if (link) msg += `\nDevam için: ${link[1]}`;
|
|
84
|
+
msg +=
|
|
85
|
+
'\n(çözüm: sağlayıcı panelinden bakiyeden kullanımı aç, başka model/sağlayıcı seç ya da sıfırlanmayı bekle)';
|
|
86
|
+
return msg;
|
|
87
|
+
}
|
|
68
88
|
const hint = HINTS[status];
|
|
69
89
|
let msg = `HTTP ${status} ${statusText}`;
|
|
70
90
|
if (hint) msg += ` — ${hint}`;
|
|
@@ -72,9 +92,33 @@ function friendlyError(status, statusText, detail) {
|
|
|
72
92
|
if ([500, 502, 503, 504].includes(status)) {
|
|
73
93
|
msg += '\n(geçici olabilir: birkaç saniye sonra tekrar dene ya da model seçiciyi açıp başka bir model dene)';
|
|
74
94
|
}
|
|
95
|
+
if (isTransientDetail(status, d)) {
|
|
96
|
+
msg += '\n(sağlayıcı geçici hatası — otomatik tekrar denenir; sürerse başka model/sağlayıcı dene)';
|
|
97
|
+
}
|
|
75
98
|
return msg;
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
/* SAĞLAYICI GEÇİCİ HATASI: bazı sağlayıcılar upstream arızasını HTTP 400
|
|
102
|
+
gövdesinde "server_error" olarak döner — istemci hatası değildir, tekrar
|
|
103
|
+
denenmeli. Bu bayrak olmadan 400 kalıcı sayılıp model boşuna "çökmüş" olur. */
|
|
104
|
+
function isTransientDetail(status, detail) {
|
|
105
|
+
if (status !== 400 && status !== 500) return false;
|
|
106
|
+
return /"(type|code)"\s*:\s*"server_error"|Error from provider|Upstream (request failed|response was not valid JSON)|temporar/i.test(
|
|
107
|
+
String(detail || '')
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* HTTP hatasını zengin bayraklarla kur — usageLimit/transient bilgisi retry ve
|
|
112
|
+
FALLOUT katmanlarına taşınır (boşa tekrar/boşa çökme olmaz). */
|
|
113
|
+
function httpError(status, statusText, detail, headers) {
|
|
114
|
+
const err = new Error(friendlyError(status, statusText, detail));
|
|
115
|
+
err.status = status;
|
|
116
|
+
if (isUsageLimitDetail(detail)) err.usageLimit = true;
|
|
117
|
+
if (isTransientDetail(status, detail)) err.transient = true;
|
|
118
|
+
err.retryAfterMs = parseRetryAfter(headers && (headers.get('retry-after-ms') || headers.get('retry-after')));
|
|
119
|
+
return err;
|
|
120
|
+
}
|
|
121
|
+
|
|
78
122
|
/* opencode cache disiplini (provider/transform.ts:1262-1276 port): destekleyen
|
|
79
123
|
sağlayıcılara oturum-sabit önbellek anahtarı gönderilir — aynı önek tekrar
|
|
80
124
|
kullanıldığında girdi tokenleri cache'ten okunur (~%90 ucuz + hızlı TTFT) */
|
|
@@ -302,9 +346,15 @@ async function withRetries(fn, { signal, onRetry } = {}) {
|
|
|
302
346
|
return await fn();
|
|
303
347
|
} catch (e) {
|
|
304
348
|
const aborted = e && (e.name === 'AbortError' || (signal && signal.aborted));
|
|
349
|
+
if (aborted) throw e;
|
|
350
|
+
/* AYLIK KOTA DOLDU: tekrar denemek boşa — hemen fırlat ki motor
|
|
351
|
+
FALLOUT ile sıradaki sağlayıcıya/modele geçebilsin. */
|
|
352
|
+
if (e && e.usageLimit) throw e;
|
|
305
353
|
const status = e && e.status;
|
|
306
|
-
const netErr =
|
|
307
|
-
|
|
354
|
+
const netErr = status === undefined;
|
|
355
|
+
/* 400 gövdesinde "server_error/upstream" diyen sağlayıcı hataları geçicidir
|
|
356
|
+
(transient) — kalıcı 400 gibi davranıp modeli boşuna "çökertme". */
|
|
357
|
+
const retriable = status === undefined || RETRYABLE_STATUS.has(status) || !!(e && e.transient);
|
|
308
358
|
if (!retriable) throw e;
|
|
309
359
|
const cap = netErr ? NET_MAX_RETRIES : MAX_RETRIES;
|
|
310
360
|
if (attempt >= cap) throw e;
|
|
@@ -409,12 +459,7 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, drops =
|
|
|
409
459
|
try {
|
|
410
460
|
detail = (await r.text()).slice(0, 300);
|
|
411
461
|
} catch {}
|
|
412
|
-
|
|
413
|
-
err.status = r.status;
|
|
414
|
-
err.retryAfterMs = parseRetryAfter(
|
|
415
|
-
r.headers && (r.headers.get('retry-after-ms') || r.headers.get('retry-after'))
|
|
416
|
-
);
|
|
417
|
-
throw err;
|
|
462
|
+
throw httpError(r.status, r.statusText, detail, r.headers);
|
|
418
463
|
}
|
|
419
464
|
return r;
|
|
420
465
|
},
|
|
@@ -590,12 +635,7 @@ async function chatOnce(sel, body, { signal, onRetry, onParamDrop, onEffortDowng
|
|
|
590
635
|
try {
|
|
591
636
|
detail = (await r.text()).slice(0, 300);
|
|
592
637
|
} catch {}
|
|
593
|
-
|
|
594
|
-
err.status = r.status;
|
|
595
|
-
err.retryAfterMs = parseRetryAfter(
|
|
596
|
-
r.headers && (r.headers.get('retry-after-ms') || r.headers.get('retry-after'))
|
|
597
|
-
);
|
|
598
|
-
throw err;
|
|
638
|
+
throw httpError(r.status, r.statusText, detail, r.headers);
|
|
599
639
|
}
|
|
600
640
|
return r;
|
|
601
641
|
},
|