beast-agent 1.9.0 → 2.1.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 -130
- 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 +106 -14
- package/src/agent/kb.js +123 -123
- package/src/agent/llm.js +610 -430
- package/src/agent/logger.js +90 -90
- package/src/agent/mcp.js +427 -427
- 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 -652
- 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 +348 -6
- package/src/preload.js +9 -0
- package/src/renderer/browserPreload.js +73 -73
- package/src/renderer/i18n.js +4 -0
- package/src/renderer/index.html +448 -409
- package/src/renderer/renderer.js +824 -41
- package/src/renderer/style.css +264 -5
package/src/agent/llm.js
CHANGED
|
@@ -1,430 +1,610 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* OpenAI-compatible streaming chat client with SSE parsing.
|
|
4
|
-
Geçici sağlayıcı hatalarında (5xx/429) üstel beklemeyle retry; İNTERNET
|
|
5
|
-
KOPMASINDA (fetch failed) çok daha sabırlı: 6 deneme + internet dönene
|
|
6
|
-
dek bekleme (main'deki net izleyicisinden beslenir). Akış ortasında kopan
|
|
7
|
-
bağlantıda: hiç veri akmadıysa sessiz baştan dener, kısmi metin geldiyse
|
|
8
|
-
'length' gibi döner → chatStreamAuto kaldığı yerden DEVAM ETTİRİR.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (!
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
return
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* OpenAI-compatible streaming chat client with SSE parsing.
|
|
4
|
+
Geçici sağlayıcı hatalarında (5xx/429) üstel beklemeyle retry; İNTERNET
|
|
5
|
+
KOPMASINDA (fetch failed) çok daha sabırlı: 6 deneme + internet dönene
|
|
6
|
+
dek bekleme (main'deki net izleyicisinden beslenir). Akış ortasında kopan
|
|
7
|
+
bağlantıda: hiç veri akmadıysa sessiz baştan dener, kısmi metin geldiyse
|
|
8
|
+
'length' gibi döner → chatStreamAuto kaldığı yerden DEVAM ETTİRİR.
|
|
9
|
+
Model reddettiği parametre/seviyeleri (reasoning_effort, temperature,
|
|
10
|
+
prompt_cache_key) 400'den otomatik öğrenir → model-caps.json'a kalıcı. */
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
const RETRYABLE_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
15
|
+
const MAX_RETRIES = 3; /* sağlayıcı geçici HTTP hataları */
|
|
16
|
+
const NET_MAX_RETRIES = 6; /* bağlantı kopması — daha sabırlı */
|
|
17
|
+
const NET_BACKOFF = [1000, 2000, 4000, 8000, 15000, 25000]; /* ≈55 sn toplam */
|
|
18
|
+
const NET_WAIT_CAP_MS = 120000; /* internet dönene dek bekleme üst sınırı */
|
|
19
|
+
|
|
20
|
+
/* main process'teki net izleyicisi besler: () => boolean | Promise<boolean> */
|
|
21
|
+
let _netProbe = null;
|
|
22
|
+
function setNetProbe(fn) {
|
|
23
|
+
_netProbe = typeof fn === 'function' ? fn : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* fetch failed / ENOTFOUND / ECONNRESET / socket hang up / terminated —
|
|
27
|
+
HTTP yanıtı ALINAMADI demektir (status yok) */
|
|
28
|
+
function isNetworkError(e) {
|
|
29
|
+
if (!e) return false;
|
|
30
|
+
if (e.name === 'AbortError') return false;
|
|
31
|
+
if (e.status !== undefined) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* internet yoksa dönene kadar bekle (en fazla NET_WAIT_CAP_MS); probe yoksa
|
|
36
|
+
ya da durum bilinmiyorsa hemen geç */
|
|
37
|
+
async function waitForNet(signal) {
|
|
38
|
+
if (!_netProbe) return true;
|
|
39
|
+
const t0 = Date.now();
|
|
40
|
+
for (;;) {
|
|
41
|
+
if (signal && signal.aborted) {
|
|
42
|
+
const e = new Error('iptal');
|
|
43
|
+
e.name = 'AbortError';
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
let online;
|
|
47
|
+
try { online = await _netProbe(); } catch { online = null; }
|
|
48
|
+
if (online !== false) return true; /* false DEĞİLSE devam (bilinmiyor = iyimser) */
|
|
49
|
+
if (Date.now() - t0 > NET_WAIT_CAP_MS) return false;
|
|
50
|
+
await sleep(2000, signal);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const HINTS = {
|
|
55
|
+
401: 'API anahtarı geçersiz',
|
|
56
|
+
402: 'kredi/bakiye bitti',
|
|
57
|
+
403: 'erişim reddedildi (anahtar yetkisi)',
|
|
58
|
+
404: 'model adı ya da endpoint yanlış',
|
|
59
|
+
429: 'hız limitine takıldın',
|
|
60
|
+
500: 'sağlayıcı iç hatası',
|
|
61
|
+
502: 'sağlayıcı upstream bağlantısı koptu',
|
|
62
|
+
503: 'sağlayıcı şu an kullanılamıyor (aşırı yük veya kapalı)',
|
|
63
|
+
504: 'sağlayıcı zaman aşımı',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function friendlyError(status, statusText, detail) {
|
|
67
|
+
const hint = HINTS[status];
|
|
68
|
+
let msg = `HTTP ${status} ${statusText}`;
|
|
69
|
+
if (hint) msg += ` — ${hint}`;
|
|
70
|
+
if (detail) msg += `\n${detail}`;
|
|
71
|
+
if ([500, 502, 503, 504].includes(status)) {
|
|
72
|
+
msg += '\n(geçici olabilir: birkaç saniye sonra tekrar dene ya da model seçiciyi açıp başka bir model dene)';
|
|
73
|
+
}
|
|
74
|
+
return msg;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* opencode cache disiplini (provider/transform.ts:1262-1276 port): destekleyen
|
|
78
|
+
sağlayıcılara oturum-sabit önbellek anahtarı gönderilir — aynı önek tekrar
|
|
79
|
+
kullanıldığında girdi tokenleri cache'ten okunur (~%90 ucuz + hızlı TTFT) */
|
|
80
|
+
const CACHE_KEY_PROVIDER_RE = /openai|azure|xai|deepseek|cerebras|deepinfra|mistral|venice/i;
|
|
81
|
+
const CACHE_KEY_MODEL_RE = /openai|gpt-|^o[134]|xai|grok|deepseek|cerebras|deepinfra|mistral|venice/i;
|
|
82
|
+
function wantsCacheKey(sel) {
|
|
83
|
+
if (!sel) return false;
|
|
84
|
+
return (
|
|
85
|
+
CACHE_KEY_PROVIDER_RE.test(String(sel.providerId || '')) ||
|
|
86
|
+
CACHE_KEY_MODEL_RE.test(String(sel.model || ''))
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ---------- model yetenek keşfi (opencode provider.metadata muadili) ----------
|
|
91
|
+
Her model her parametreyi kabul etmez: Mimo gibi bazıları reasoning_effort'u,
|
|
92
|
+
GPT-5/o1 ailesi temperature'ı reddeder. Sağlayıcı 400 "Invalid request
|
|
93
|
+
parameters" diyorsa parametre TEK TEK atlanıp sessizce yeniden denenir;
|
|
94
|
+
kabul edilen kombinasyon KALICI öğrenilir (model-caps.json) → sonraki
|
|
95
|
+
isteklerde reddedilen parametre hiç gönderilmez. models.dev metadata'sına
|
|
96
|
+
internet bağımlılığı olmadan çalışma-zamanında eşdeğer davranır. */
|
|
97
|
+
|
|
98
|
+
const CAPS_FLAGS = ['noReasoning', 'noTemperature', 'noCacheKey'];
|
|
99
|
+
const PARAM_LABELS = {
|
|
100
|
+
noReasoning: 'reasoning_effort',
|
|
101
|
+
noTemperature: 'temperature',
|
|
102
|
+
noCacheKey: 'prompt_cache_key',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
let _capsFile = null;
|
|
106
|
+
const _caps = new Map(); /* "providerId::model" -> { noReasoning?, noTemperature?, noCacheKey? } */
|
|
107
|
+
|
|
108
|
+
function capsKey(sel) {
|
|
109
|
+
return String((sel && sel.providerId) || '') + '::' + String((sel && sel.model) || '');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function capsFor(sel) {
|
|
113
|
+
return _caps.get(capsKey(sel)) || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function resetCaps() {
|
|
117
|
+
_caps.clear();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* caps dosyasını bağla: mevcut öğrenimler yüklenir; null → kapat */
|
|
121
|
+
function setCapsFile(p) {
|
|
122
|
+
_caps.clear();
|
|
123
|
+
_capsFile = typeof p === 'string' && p ? p : null;
|
|
124
|
+
if (!_capsFile) return;
|
|
125
|
+
try {
|
|
126
|
+
const j = JSON.parse(fs.readFileSync(_capsFile, 'utf8'));
|
|
127
|
+
for (const [k, v] of Object.entries(j.models || {})) {
|
|
128
|
+
if (v && typeof v === 'object') _caps.set(k, { ...v });
|
|
129
|
+
}
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function learnCaps(sel, drops) {
|
|
134
|
+
const k = capsKey(sel);
|
|
135
|
+
const prev = _caps.get(k) || {};
|
|
136
|
+
const next = { ...prev };
|
|
137
|
+
let changed = false;
|
|
138
|
+
for (const f of CAPS_FLAGS) {
|
|
139
|
+
if (drops[f] && !prev[f]) {
|
|
140
|
+
next[f] = true;
|
|
141
|
+
changed = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/* reddedilen reasoning seviyeleri (["max","xhigh",...]) — seviye bazlı öğrenim */
|
|
145
|
+
if (Array.isArray(drops.failedEfforts) && drops.failedEfforts.length) {
|
|
146
|
+
const merged = [...(prev.failedEfforts || [])];
|
|
147
|
+
for (const e of drops.failedEfforts) {
|
|
148
|
+
if (!merged.includes(e)) merged.push(e);
|
|
149
|
+
}
|
|
150
|
+
if (merged.length !== (prev.failedEfforts || []).length) {
|
|
151
|
+
next.failedEfforts = merged;
|
|
152
|
+
changed = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!changed) return;
|
|
156
|
+
_caps.set(k, next);
|
|
157
|
+
if (!_capsFile) return;
|
|
158
|
+
try {
|
|
159
|
+
const all = {};
|
|
160
|
+
for (const [key, v] of _caps) all[key] = v;
|
|
161
|
+
fs.writeFileSync(_capsFile, JSON.stringify({ version: 1, models: all }, null, 2));
|
|
162
|
+
} catch {}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* reasoning_effort merdiveni: reddedilen seviyede BİR ALT seviye denenir
|
|
166
|
+
(Mimo 2.5: max/xhigh reddediyor, low çalışıyor). low'un altı yok → null. */
|
|
167
|
+
const EFFORT_LOWER = { max: 'xhigh', xhigh: 'high', high: 'medium', medium: 'low', low: null };
|
|
168
|
+
|
|
169
|
+
/* Hangi hata metni "geçersiz istek" dili (Mimo: "Invalid request parameters").
|
|
170
|
+
friendlyError'ın kendi öneki ("HTTP 400 Bad Request") ile eşleşmemesine dikkat. */
|
|
171
|
+
const GENERIC_400_RE =
|
|
172
|
+
/invalid request param|invalid_param|unsupported|not supported|unknown (param|field|argument)|unrecognized|unexpected/i;
|
|
173
|
+
|
|
174
|
+
/* run(drops, body): verilen atlama seti + gövdeyle isteği çalıştırır.
|
|
175
|
+
400 gelirse ve hata parametre reddine işaret ediyorsa:
|
|
176
|
+
1) reasoning_effort önce SEVİYE düşürülerek denenir (max→xhigh→...→low),
|
|
177
|
+
en düşük de reddedilirse parametre tamamen bırakılır (noReasoning);
|
|
178
|
+
2) sonra temperature, prompt_cache_key atlanır;
|
|
179
|
+
Kabul edilen kombinasyon bu model için KALICI öğrenilir → sonraki isteklerde
|
|
180
|
+
reddedilen seviye/parametre hiç gönderilmez (otomatik reasoning uyumu). */
|
|
181
|
+
async function withParamFallbacks(sel, body0, run, { signal, onParamDrop, onEffortDowngrade } = {}) {
|
|
182
|
+
const active = { ...(capsFor(sel) || {}) };
|
|
183
|
+
for (;;) {
|
|
184
|
+
/* seçilen seviyeyi öğrenilen redlere göre otomatik düşür */
|
|
185
|
+
let eff = body0.reasoningEffort ? String(body0.reasoningEffort) : null;
|
|
186
|
+
if (eff && !active.noReasoning) {
|
|
187
|
+
const failed = active.failedEfforts || [];
|
|
188
|
+
let guard = 0;
|
|
189
|
+
while (eff && failed.includes(eff) && guard++ < 5) eff = EFFORT_LOWER[eff] || null;
|
|
190
|
+
}
|
|
191
|
+
if (active.noReasoning) eff = null;
|
|
192
|
+
const body = eff && eff !== body0.reasoningEffort ? { ...body0, reasoningEffort: eff } : body0;
|
|
193
|
+
let res;
|
|
194
|
+
try {
|
|
195
|
+
res = await run({ ...active }, body);
|
|
196
|
+
learnCaps(sel, active);
|
|
197
|
+
return res;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (!e || e.status !== 400 || (signal && signal.aborted)) throw e;
|
|
200
|
+
const msg = String(e.message || '');
|
|
201
|
+
const generic = GENERIC_400_RE.test(msg);
|
|
202
|
+
const reasonHint = /reason|effort|thinking/i.test(msg);
|
|
203
|
+
const tempHint = /temperature/i.test(msg);
|
|
204
|
+
|
|
205
|
+
/* denenecek atlamalar öncelik sırasıyla: önce reasoning merdiveni
|
|
206
|
+
(seviye düşür — Mimo: max reddedilir, low çalışır), sonra diğerleri */
|
|
207
|
+
const actions = [];
|
|
208
|
+
if ((reasonHint || generic) && body0.reasoningEffort && !active.noReasoning) actions.push('reasoning');
|
|
209
|
+
if ((generic || tempHint) && !active.noTemperature) actions.push('temperature');
|
|
210
|
+
if ((generic || /cache/i.test(msg)) && body0.cacheKey && wantsCacheKey(sel) && !active.noCacheKey) {
|
|
211
|
+
actions.push('cacheKey');
|
|
212
|
+
}
|
|
213
|
+
const act = actions[0];
|
|
214
|
+
if (!act) throw e;
|
|
215
|
+
|
|
216
|
+
if (act === 'reasoning') {
|
|
217
|
+
const cur = eff;
|
|
218
|
+
const lower = cur && cur in EFFORT_LOWER ? EFFORT_LOWER[cur] : null;
|
|
219
|
+
/* merdiven: generic red (gerçek sebep bilinmiyor → seviyeleri dene)
|
|
220
|
+
ya da red mesajı SEVİYE değerini zikrediyorsa ("effort 'max' invalid").
|
|
221
|
+
Mesaj parametrenin kendisini reddediyorsa ("not supported") direkt bırak. */
|
|
222
|
+
const valueQuoted = cur ? (msg.includes(`'${cur}'`) || msg.includes(`"${cur}"`)) : false;
|
|
223
|
+
/* ipucu parametrenin kendisini reddediyorsa merdivene gerek yok */
|
|
224
|
+
const ladder = valueQuoted || (generic && !reasonHint);
|
|
225
|
+
if (cur && lower && ladder) {
|
|
226
|
+
/* seviye reddedildi → bir alt seviyeye in, kalıcı öğren */
|
|
227
|
+
if (!Array.isArray(active.failedEfforts)) active.failedEfforts = [];
|
|
228
|
+
if (!active.failedEfforts.includes(cur)) {
|
|
229
|
+
active.failedEfforts.push(cur);
|
|
230
|
+
learnCaps(sel, active);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
onEffortDowngrade && onEffortDowngrade(cur, lower);
|
|
234
|
+
} catch {}
|
|
235
|
+
} else {
|
|
236
|
+
/* parametre tamamen reddedildi → bir daha gönderme */
|
|
237
|
+
active.noReasoning = true;
|
|
238
|
+
try {
|
|
239
|
+
onParamDrop && onParamDrop('noReasoning');
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
242
|
+
} else if (act === 'temperature') {
|
|
243
|
+
active.noTemperature = true;
|
|
244
|
+
try {
|
|
245
|
+
onParamDrop && onParamDrop('noTemperature');
|
|
246
|
+
} catch {}
|
|
247
|
+
} else if (act === 'cacheKey') {
|
|
248
|
+
active.noCacheKey = true;
|
|
249
|
+
try {
|
|
250
|
+
onParamDrop && onParamDrop('noCacheKey');
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Retry-After başlığı: saniye ("2"), milisaniye ("120ms" / retry-after-ms)
|
|
258
|
+
ya da HTTP-tarih olabilir (opencode session/retry.ts port) */
|
|
259
|
+
function parseRetryAfter(v) {
|
|
260
|
+
if (v == null) return null;
|
|
261
|
+
const s = String(v).trim();
|
|
262
|
+
if (!s) return null;
|
|
263
|
+
const ms = /^(\d+)\s*ms$/i.exec(s);
|
|
264
|
+
if (ms) return Number(ms[1]);
|
|
265
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Number(s) * 1000;
|
|
266
|
+
const d = Date.parse(s);
|
|
267
|
+
if (!Number.isNaN(d)) return Math.max(0, d - Date.now());
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const RETRY_AFTER_CAP_MS = 30000; /* opencode retry.ts:39 — headersız bekleme tavanı */
|
|
272
|
+
function clampMs(v) {
|
|
273
|
+
return Math.min(RETRY_AFTER_CAP_MS, Math.max(0, Number(v) || 0));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function sleep(ms, signal) {
|
|
277
|
+
return new Promise((resolve, reject) => {
|
|
278
|
+
const t = setTimeout(resolve, ms);
|
|
279
|
+
if (signal) {
|
|
280
|
+
signal.addEventListener(
|
|
281
|
+
'abort',
|
|
282
|
+
() => {
|
|
283
|
+
clearTimeout(t);
|
|
284
|
+
const e = new Error('iptal');
|
|
285
|
+
e.name = 'AbortError';
|
|
286
|
+
reject(e);
|
|
287
|
+
},
|
|
288
|
+
{ once: true }
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* fn: denenecek istek. Sağlayıcı geçici HTTP hatalarında 0.8s→1.6s→3.2s;
|
|
295
|
+
İNTERNET KOPMASINDA (status yok) 6 denemeye kadar 1s→2s→4s→8s→15s→25s ve
|
|
296
|
+
net izleyicisi "çevrimdışı" diyorsa internet dönene kadar bekler. */
|
|
297
|
+
async function withRetries(fn, { signal, onRetry } = {}) {
|
|
298
|
+
let attempt = 0;
|
|
299
|
+
for (;;) {
|
|
300
|
+
try {
|
|
301
|
+
return await fn();
|
|
302
|
+
} catch (e) {
|
|
303
|
+
const aborted = e && (e.name === 'AbortError' || (signal && signal.aborted));
|
|
304
|
+
const status = e && e.status;
|
|
305
|
+
const netErr = !aborted && status === undefined;
|
|
306
|
+
const retriable = !aborted && (status === undefined || RETRYABLE_STATUS.has(status));
|
|
307
|
+
if (!retriable) throw e;
|
|
308
|
+
const cap = netErr ? NET_MAX_RETRIES : MAX_RETRIES;
|
|
309
|
+
if (attempt >= cap) throw e;
|
|
310
|
+
if (netErr && _netProbe) {
|
|
311
|
+
/* internet kopmuş: dönene kadar bekle — kısa kopmalarda görev ölmez */
|
|
312
|
+
await waitForNet(signal);
|
|
313
|
+
}
|
|
314
|
+
attempt++;
|
|
315
|
+
try {
|
|
316
|
+
onRetry && onRetry(attempt, status);
|
|
317
|
+
} catch {}
|
|
318
|
+
/* sağlayıcı retry-after dediye: backoff yerine ona uy (30 sn tavan);
|
|
319
|
+
opencode session/retry.ts:26-31 ile aynı politika */
|
|
320
|
+
let wait;
|
|
321
|
+
if (e.retryAfterMs) wait = clampMs(e.retryAfterMs);
|
|
322
|
+
else
|
|
323
|
+
wait = netErr
|
|
324
|
+
? NET_BACKOFF[Math.min(attempt - 1, NET_BACKOFF.length - 1)]
|
|
325
|
+
: 800 * Math.pow(2, attempt - 1);
|
|
326
|
+
await sleep(wait, signal);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function openChat(sel, body, { stream, signal, drops } = {}) {
|
|
332
|
+
const d = drops || {};
|
|
333
|
+
const payload = {
|
|
334
|
+
model: sel.model,
|
|
335
|
+
messages: body.messages,
|
|
336
|
+
stream,
|
|
337
|
+
...(body.tools && body.tools.length ? { tools: body.tools } : {}),
|
|
338
|
+
};
|
|
339
|
+
if (!d.noTemperature) {
|
|
340
|
+
payload.temperature = body.temperature ?? 0.6;
|
|
341
|
+
}
|
|
342
|
+
/* Düşünme (reasoning) seviyesi: OpenAI-style reasoning_effort —
|
|
343
|
+
OpenRouter ve GPT-5 ailesi dahil uyumlu sağlayıcılar kabul eder.
|
|
344
|
+
Kabul etmeyenlerde (Mimo vb.): 400 → parametre atlanır + KALICI öğrenilir. */
|
|
345
|
+
if (body.reasoningEffort && !d.noReasoning) {
|
|
346
|
+
payload.reasoning_effort = String(body.reasoningEffort);
|
|
347
|
+
}
|
|
348
|
+
/* prompt cache anahtarı: oturum başına sabit → sağlayıcı önek önbelleği tutar */
|
|
349
|
+
if (body.cacheKey && wantsCacheKey(sel) && !d.noCacheKey) {
|
|
350
|
+
payload.prompt_cache_key = String(body.cacheKey);
|
|
351
|
+
}
|
|
352
|
+
return fetch(sel.url, {
|
|
353
|
+
method: 'POST',
|
|
354
|
+
signal,
|
|
355
|
+
headers: {
|
|
356
|
+
'Content-Type': 'application/json',
|
|
357
|
+
Authorization: `Bearer ${sel.key}`,
|
|
358
|
+
'HTTP-Referer': 'https://localhost/beast',
|
|
359
|
+
'X-Title': 'Beast Agent',
|
|
360
|
+
},
|
|
361
|
+
body: JSON.stringify(payload),
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function chatStream(sel, body, { signal, onDelta, onRetry, onParamDrop, onEffortDowngrade } = {}) {
|
|
366
|
+
/* 400 parametre/seviye reddinde: otomatik uyum (seviye düşür → parametre atla),
|
|
367
|
+
başarılı set kalıcı öğrenilir */
|
|
368
|
+
return withParamFallbacks(
|
|
369
|
+
sel,
|
|
370
|
+
body,
|
|
371
|
+
(drops, b) => streamOnce(sel, b, { signal, onDelta }, drops),
|
|
372
|
+
{ signal, onParamDrop, onEffortDowngrade }
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, drops = {}) {
|
|
377
|
+
/* akış durumu dışarıda tutulur: gövde okunurken bağlantı koparsa
|
|
378
|
+
ne kadarı geldiğine bakılır (kısmi metin → devam; boş/araç → baştan) */
|
|
379
|
+
const state = { content: '', reasoning: '', toolCalls: [], usage: null, finishReason: null };
|
|
380
|
+
let dropAttempt = 0;
|
|
381
|
+
|
|
382
|
+
for (;;) {
|
|
383
|
+
const res = await withRetries(
|
|
384
|
+
async () => {
|
|
385
|
+
const r = await openChat(sel, body, { stream: true, signal, drops });
|
|
386
|
+
if (!r.ok) {
|
|
387
|
+
let detail = '';
|
|
388
|
+
try {
|
|
389
|
+
detail = (await r.text()).slice(0, 300);
|
|
390
|
+
} catch {}
|
|
391
|
+
const err = new Error(friendlyError(r.status, r.statusText, detail));
|
|
392
|
+
err.status = r.status;
|
|
393
|
+
err.retryAfterMs = parseRetryAfter(
|
|
394
|
+
r.headers && (r.headers.get('retry-after-ms') || r.headers.get('retry-after'))
|
|
395
|
+
);
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
return r;
|
|
399
|
+
},
|
|
400
|
+
{ signal, onRetry }
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const reader = res.body.getReader();
|
|
405
|
+
const decoder = new TextDecoder();
|
|
406
|
+
let buf = '';
|
|
407
|
+
/* SERT İPTAL: abort gelirse gövdeyi fiziksel kapat (reader.cancel) —
|
|
408
|
+
sağlayıcı/fetch abort'u gövdeye taşımasa bile okuma döngüsü ANINDA
|
|
409
|
+
biter, token akışı kesilir */
|
|
410
|
+
let onAbort = null;
|
|
411
|
+
if (signal) {
|
|
412
|
+
onAbort = () => {
|
|
413
|
+
try {
|
|
414
|
+
const p = reader.cancel();
|
|
415
|
+
if (p && typeof p.catch === 'function') p.catch(() => {});
|
|
416
|
+
} catch {}
|
|
417
|
+
};
|
|
418
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
/* tek satır SSE olayını işle — hem okuma döngüsü hem SON FLUSH kullanır */
|
|
422
|
+
const handleLine = (line) => {
|
|
423
|
+
line = line.trim();
|
|
424
|
+
if (!line.startsWith('data:')) return;
|
|
425
|
+
const data = line.slice(5).trim();
|
|
426
|
+
if (!data || data === '[DONE]') return;
|
|
427
|
+
|
|
428
|
+
let json;
|
|
429
|
+
try {
|
|
430
|
+
json = JSON.parse(data);
|
|
431
|
+
} catch {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (json.usage) state.usage = json.usage;
|
|
436
|
+
const ch = json.choices && json.choices[0];
|
|
437
|
+
if (!ch) return;
|
|
438
|
+
if (ch.finish_reason) state.finishReason = ch.finish_reason;
|
|
439
|
+
const d = ch.delta || {};
|
|
440
|
+
if (d.content) {
|
|
441
|
+
state.content += d.content;
|
|
442
|
+
onDelta && onDelta(d.content, state.content);
|
|
443
|
+
}
|
|
444
|
+
if (d.reasoning_content) state.reasoning += d.reasoning_content;
|
|
445
|
+
if (d.reasoning) state.reasoning += d.reasoning;
|
|
446
|
+
for (const tc of d.tool_calls || []) {
|
|
447
|
+
const i = typeof tc.index === 'number' ? tc.index : state.toolCalls.length;
|
|
448
|
+
while (state.toolCalls.length <= i) {
|
|
449
|
+
state.toolCalls.push({ id: '', type: 'function', function: { name: '', arguments: '' } });
|
|
450
|
+
}
|
|
451
|
+
if (tc.id) state.toolCalls[i].id = tc.id;
|
|
452
|
+
if (tc.function) {
|
|
453
|
+
if (tc.function.name) state.toolCalls[i].function.name += tc.function.name;
|
|
454
|
+
if (tc.function.arguments) state.toolCalls[i].function.arguments += tc.function.arguments;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
while (true) {
|
|
460
|
+
const { done, value } = await reader.read();
|
|
461
|
+
if (signal && signal.aborted) {
|
|
462
|
+
const e = new Error('iptal');
|
|
463
|
+
e.name = 'AbortError';
|
|
464
|
+
throw e;
|
|
465
|
+
}
|
|
466
|
+
if (done) break;
|
|
467
|
+
buf += decoder.decode(value, { stream: true });
|
|
468
|
+
|
|
469
|
+
let idx;
|
|
470
|
+
while ((idx = buf.indexOf('\n')) !== -1) {
|
|
471
|
+
const line = buf.slice(0, idx);
|
|
472
|
+
buf = buf.slice(idx + 1);
|
|
473
|
+
handleLine(line);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* SON FLUSH — KRİTİK: akış son parçası '\n' ile bitmeyebilir.
|
|
478
|
+
Okuma bitince kalan buf'u Decoder'dan boşaltıp işlemezsek
|
|
479
|
+
cevabın SON kelimesi/cümlesi kaybolur (SSE sonu öyle gelir). */
|
|
480
|
+
buf += decoder.decode();
|
|
481
|
+
if (buf.trim()) handleLine(buf);
|
|
482
|
+
|
|
483
|
+
return { ...state };
|
|
484
|
+
} finally {
|
|
485
|
+
if (signal && onAbort) {
|
|
486
|
+
try { signal.removeEventListener('abort', onAbort); } catch {}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
} catch (e) {
|
|
490
|
+
/* okuma sırasında bağlantı koptu */
|
|
491
|
+
const aborted = e && (e.name === 'AbortError' || (signal && signal.aborted));
|
|
492
|
+
if (!aborted && isNetworkError(e)) {
|
|
493
|
+
if (state.content && !state.toolCalls.length) {
|
|
494
|
+
/* kısmi metin geldi, araç çağrısı yok → hata balonu YOK:
|
|
495
|
+
'length' gibi dön, chatStreamAuto CONTINUE_PROMPT ile kaldığı
|
|
496
|
+
yerden sürdürür ve metni birleştirir */
|
|
497
|
+
return { ...state, finishReason: 'length' };
|
|
498
|
+
}
|
|
499
|
+
/* hiç veri akmadı YA DA araç çağrısı yarım kaldı → BAŞTAN dene */
|
|
500
|
+
if (dropAttempt >= NET_MAX_RETRIES) throw e;
|
|
501
|
+
dropAttempt++;
|
|
502
|
+
try { onRetry && onRetry(dropAttempt, undefined); } catch {}
|
|
503
|
+
if (_netProbe) await waitForNet(signal);
|
|
504
|
+
await sleep(NET_BACKOFF[Math.min(dropAttempt - 1, NET_BACKOFF.length - 1)], signal);
|
|
505
|
+
continue; /* yeni stream — UI'daki kısmi token'lar nihai mesajla ezilir */
|
|
506
|
+
}
|
|
507
|
+
throw e;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/* usage toplama: devam turları gerçek faturalamayı yansıtsın (prompt yeniden sayılır) */
|
|
513
|
+
function sumUsage(a, b) {
|
|
514
|
+
if (!a) return b || null;
|
|
515
|
+
if (!b) return a;
|
|
516
|
+
const out = {};
|
|
517
|
+
for (const k of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
518
|
+
if (typeof a[k] === 'number' && typeof b[k] === 'number') out[k] = a[k] + b[k];
|
|
519
|
+
else out[k] = b[k] !== undefined ? b[k] : a[k];
|
|
520
|
+
}
|
|
521
|
+
return out;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* OTOMATİK DEVAM: model cevabı çıkış limiti (finish_reason='length') yüzünden
|
|
525
|
+
kelime ortasında kesilirse — özellikle free modellerin düşük varsayılan
|
|
526
|
+
limitlerinde olur — yarım cümle kullanıcıya gitmesin. Kaldığı yerden devam
|
|
527
|
+
istenip metin birleştirilir (en fazla 2 devam turu). Araç çağrılı yanıtlarda
|
|
528
|
+
ve içerik hiç gelmediyse devreye girmez; orijinal mesaj dizisi DEĞİŞTİRİLMEZ. */
|
|
529
|
+
const CONTINUE_PROMPT =
|
|
530
|
+
'Yanıtın çıkış limiti nedeniyle ortadan kesildi. Kaldığın yerden DEVAM ET — baştan başlama, özetleme, açıklama yapma; yalnızca kesilen kısmın DEVAMINI yaz.';
|
|
531
|
+
|
|
532
|
+
async function chatStreamAuto(sel, body, opts = {}) {
|
|
533
|
+
const first = await chatStream(sel, body, opts);
|
|
534
|
+
if (first.finishReason !== 'length' || !first.content || (first.toolCalls && first.toolCalls.length)) {
|
|
535
|
+
return first;
|
|
536
|
+
}
|
|
537
|
+
let full = first.content;
|
|
538
|
+
let last = first;
|
|
539
|
+
for (let i = 0; i < 2; i++) {
|
|
540
|
+
const msgs2 = body.messages.slice();
|
|
541
|
+
msgs2.push({ role: 'assistant', content: full });
|
|
542
|
+
msgs2.push({ role: 'user', content: CONTINUE_PROMPT });
|
|
543
|
+
let cont;
|
|
544
|
+
try {
|
|
545
|
+
cont = await chatStream(sel, { ...body, messages: msgs2 }, opts);
|
|
546
|
+
} catch {
|
|
547
|
+
break; // devam turu patlarsa elimizdeki kısmi yanıtla döneriz
|
|
548
|
+
}
|
|
549
|
+
if (!cont.content || (cont.toolCalls && cont.toolCalls.length)) break;
|
|
550
|
+
full += cont.content;
|
|
551
|
+
last = { ...cont, content: full, usage: sumUsage(first.usage, cont.usage) };
|
|
552
|
+
if (cont.finishReason !== 'length') break;
|
|
553
|
+
}
|
|
554
|
+
return last;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async function chatOnce(sel, body, { signal, onRetry, onParamDrop, onEffortDowngrade } = {}) {
|
|
558
|
+
const res = await withParamFallbacks(
|
|
559
|
+
sel,
|
|
560
|
+
body,
|
|
561
|
+
(drops, b) =>
|
|
562
|
+
withRetries(
|
|
563
|
+
async () => {
|
|
564
|
+
const r = await openChat(sel, b, { stream: false, signal, drops });
|
|
565
|
+
if (!r.ok) {
|
|
566
|
+
let detail = '';
|
|
567
|
+
try {
|
|
568
|
+
detail = (await r.text()).slice(0, 300);
|
|
569
|
+
} catch {}
|
|
570
|
+
const err = new Error(friendlyError(r.status, r.statusText, detail));
|
|
571
|
+
err.status = r.status;
|
|
572
|
+
err.retryAfterMs = parseRetryAfter(
|
|
573
|
+
r.headers && (r.headers.get('retry-after-ms') || r.headers.get('retry-after'))
|
|
574
|
+
);
|
|
575
|
+
throw err;
|
|
576
|
+
}
|
|
577
|
+
return r;
|
|
578
|
+
},
|
|
579
|
+
{ signal, onRetry }
|
|
580
|
+
),
|
|
581
|
+
{ signal, onParamDrop, onEffortDowngrade }
|
|
582
|
+
);
|
|
583
|
+
const json = await res.json();
|
|
584
|
+
const msg = json.choices?.[0]?.message || {};
|
|
585
|
+
return {
|
|
586
|
+
content: msg.content || '',
|
|
587
|
+
reasoning: msg.reasoning_content || msg.reasoning || '',
|
|
588
|
+
toolCalls: msg.tool_calls || [],
|
|
589
|
+
usage: json.usage || null,
|
|
590
|
+
finishReason: json.choices?.[0]?.finish_reason,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
module.exports = {
|
|
595
|
+
chatStream,
|
|
596
|
+
chatStreamAuto,
|
|
597
|
+
chatOnce,
|
|
598
|
+
withRetries,
|
|
599
|
+
withParamFallbacks,
|
|
600
|
+
friendlyError,
|
|
601
|
+
sumUsage,
|
|
602
|
+
setNetProbe,
|
|
603
|
+
setCapsFile,
|
|
604
|
+
capsFor,
|
|
605
|
+
resetCaps,
|
|
606
|
+
PARAM_LABELS,
|
|
607
|
+
isNetworkError,
|
|
608
|
+
wantsCacheKey,
|
|
609
|
+
parseRetryAfter,
|
|
610
|
+
};
|