openzoo 0.49.4 → 0.49.6
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/README.md +7 -7
- package/lib/demo.js +4 -1
- package/lib/grokui.mjs +449 -26
- package/lib/gui.html +4 -1
- package/lib/livestatus.js +173 -4
- package/lib/mcp.js +3 -1
- package/lib/pay.js +3 -0
- package/lib/podagent.mjs +50 -12
- package/lib/proxy.js +114 -97
- package/lib/racesettle.js +78 -8
- package/lib/spill.js +168 -9
- package/lib/x402.js +13 -4
- package/package.json +3 -3
package/lib/gui.html
CHANGED
|
@@ -194,7 +194,10 @@ async function send() {
|
|
|
194
194
|
const x = d.x402 || {};
|
|
195
195
|
state.calls += 1;
|
|
196
196
|
if (typeof x.billedUsd === "number") state.spent += x.billedUsd;
|
|
197
|
-
if (typeof x.
|
|
197
|
+
if (typeof x.savedUsd === "number" && x.savedUsd > 0) state.saved += x.savedUsd;
|
|
198
|
+
else if (typeof x.directUsd === "number" && typeof x.billedUsd === "number") {
|
|
199
|
+
state.saved += Math.max(0, x.directUsd - x.billedUsd);
|
|
200
|
+
}
|
|
198
201
|
const bits = [];
|
|
199
202
|
if (typeof x.billedUsd === "number") bits.push(`$${x.billedUsd.toFixed(4)}`);
|
|
200
203
|
if (x.lecore?.engaged) bits.push(`🧠 ${x.lecore.recalled ?? "?"} slices`);
|
package/lib/livestatus.js
CHANGED
|
@@ -28,6 +28,68 @@ export function formatPayStatus(attempt = 0) {
|
|
|
28
28
|
return Number(attempt) > 0 ? 'waiting on x402…' : 'paying…';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** Cut from 1-model savings. X does not change it — they pay every launched racer. */
|
|
32
|
+
export function raceSavingsCutPct(y) {
|
|
33
|
+
const n = Math.max(1, Number(y) || 1);
|
|
34
|
+
return Math.round((1 - 1 / n) * 100);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sitrepUsd(n) {
|
|
38
|
+
const x = Number(n) || 0;
|
|
39
|
+
return x >= 0.01 || x === 0 ? `$${x.toFixed(2)}` : `$${x.toFixed(5)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sitrepFlight({ status, liveStatus, liveRace } = {}) {
|
|
43
|
+
if (status !== 'thinking') return 'idle';
|
|
44
|
+
if (liveRace?.phase === 'judging') return 'classifier judging';
|
|
45
|
+
if (liveRace?.phase === 'winner') return 'winner';
|
|
46
|
+
if (liveStatus) return String(liveStatus);
|
|
47
|
+
return 'in flight';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compact sitrep field list (drawer). No keys, no .npmrc.
|
|
52
|
+
* Prepaid is yes/no — never a secret.
|
|
53
|
+
*/
|
|
54
|
+
export function formatSitrep(info = {}) {
|
|
55
|
+
const y = Number(info.race) >= 2 ? Number(info.race) : 1;
|
|
56
|
+
const need = Number(info.raceNeed) || 1;
|
|
57
|
+
const spent = Number(info.spentUsd) || 0;
|
|
58
|
+
const cogs = Number(info.cogsUsd) || 0;
|
|
59
|
+
const direct = Number(info.directUsd) || 0;
|
|
60
|
+
const mult = spent > 0 ? direct / spent : null;
|
|
61
|
+
const saved = mult == null
|
|
62
|
+
? '—'
|
|
63
|
+
: `${mult >= 100 ? Math.round(mult) : mult.toFixed(mult >= 10 ? 1 : 2)}x`;
|
|
64
|
+
const credit = Number(info.creditUsd);
|
|
65
|
+
const prepaid = Number.isFinite(credit) && credit > 0 ? 'yes' : 'no';
|
|
66
|
+
return [
|
|
67
|
+
'Sitrep',
|
|
68
|
+
` race ${raceChoiceLabel(y, need)}`,
|
|
69
|
+
` band ${info.tier || 'medium'}`,
|
|
70
|
+
` mode ${info.runMode || 'ask'}`,
|
|
71
|
+
` cwd ${info.dir || '—'}`,
|
|
72
|
+
` in flight ${sitrepFlight(info)}`,
|
|
73
|
+
` paid ${sitrepUsd(spent)}`,
|
|
74
|
+
` cogs ${sitrepUsd(cogs)}`,
|
|
75
|
+
` direct ${sitrepUsd(direct)}`,
|
|
76
|
+
` saved ${saved}`,
|
|
77
|
+
` paid calls ${Number(info.paidCalls) || 0}`,
|
|
78
|
+
` prepaid ${prepaid}`,
|
|
79
|
+
].join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Picker / sitrep label: `best 2 of 4 −75%`, `1 model 0%`. */
|
|
83
|
+
export function raceChoiceLabel(y, need = 1) {
|
|
84
|
+
const n = Math.max(1, Number(y) || 1);
|
|
85
|
+
const k = Math.max(1, Math.min(Number(need) || 1, n));
|
|
86
|
+
const cut = raceSavingsCutPct(n);
|
|
87
|
+
const cutTxt = cut === 0 ? '0%' : `−${cut}%`;
|
|
88
|
+
if (n < 2) return `1 model ${cutTxt}`;
|
|
89
|
+
if (k > 1) return `best ${k} of ${n} ${cutTxt}`;
|
|
90
|
+
return `race ${n} ${cutTxt}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
31
93
|
/** First-X-back race: how many of the K we asked for have actually landed. */
|
|
32
94
|
export function formatRaceStatus(back, need) {
|
|
33
95
|
const n = Math.max(1, Number(need) || 1);
|
|
@@ -35,6 +97,24 @@ export function formatRaceStatus(back, need) {
|
|
|
35
97
|
return `racing ${b}/${n} back…`;
|
|
36
98
|
}
|
|
37
99
|
|
|
100
|
+
/** OpenRouter id → short cell label. `z-ai/glm-4.7` → `glm-4.7`. */
|
|
101
|
+
export function shortModelName(id) {
|
|
102
|
+
const s = String(id || '').trim();
|
|
103
|
+
if (!s) return 'model';
|
|
104
|
+
const parts = s.split('/');
|
|
105
|
+
return parts[parts.length - 1] || s;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Compact spectator preview — opening lines, not the whole answer. */
|
|
109
|
+
export function clipRacePreview(text, maxLines = 8, maxChars = 420) {
|
|
110
|
+
const s = String(text || '').replace(/\r/g, '');
|
|
111
|
+
if (!s) return '';
|
|
112
|
+
const lines = s.split('\n');
|
|
113
|
+
let out = lines.length > maxLines ? lines.slice(0, maxLines).join('\n') + '\n…' : s;
|
|
114
|
+
if (out.length > maxChars) out = `${out.slice(0, maxChars - 1)}…`;
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
38
118
|
/** Race-level failure when no countable answer exists. Never a single model name. */
|
|
39
119
|
export const RACE_EVERY_FAILED = '(race: every model failed — no reply)';
|
|
40
120
|
|
|
@@ -154,20 +234,76 @@ export function pickRaceWinner(cands, minScore = RACE_MIN_SCORE) {
|
|
|
154
234
|
/**
|
|
155
235
|
* Live race bubble: stream the fastest still-alive entrant, swap once if the
|
|
156
236
|
* winner is someone else. `onDelta(text, { replace, model })`.
|
|
237
|
+
*
|
|
238
|
+
* `onRace(snap)` is the spectator feed — one cell per launched model, with
|
|
239
|
+
* waiting/streaming/back/failed/abandoned plus a truncated preview. Not a
|
|
240
|
+
* second racer: `phase: 'judging'` is the classifier looking at the X that
|
|
241
|
+
* already made it back.
|
|
157
242
|
*/
|
|
158
|
-
export function createRaceFeed(onDelta, onStatus, need) {
|
|
243
|
+
export function createRaceFeed(onDelta, onStatus, need, onRace) {
|
|
159
244
|
let live = null;
|
|
160
245
|
let settled = false;
|
|
161
246
|
let back = 0;
|
|
247
|
+
let phase = 'racing';
|
|
248
|
+
let winnerModel = '';
|
|
249
|
+
let recutNote = '';
|
|
162
250
|
const buf = new Map();
|
|
163
251
|
const dead = new Set();
|
|
252
|
+
const order = [];
|
|
253
|
+
const cells = new Map();
|
|
164
254
|
const paintStatus = () => { onStatus?.(formatRaceStatus(back, need)); };
|
|
255
|
+
const ensure = (model) => {
|
|
256
|
+
const id = String(model || '').trim() || 'model';
|
|
257
|
+
if (cells.has(id)) return cells.get(id);
|
|
258
|
+
const row = { model: id, status: 'waiting', preview: '', fail: '' };
|
|
259
|
+
cells.set(id, row);
|
|
260
|
+
order.push(id);
|
|
261
|
+
return row;
|
|
262
|
+
};
|
|
263
|
+
const freezeStragglers = () => {
|
|
264
|
+
if (back < need) return;
|
|
265
|
+
for (const row of cells.values()) {
|
|
266
|
+
if (row.status === 'waiting' || row.status === 'streaming') row.status = 'abandoned';
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const snapshot = () => ({
|
|
270
|
+
need,
|
|
271
|
+
launched: order.length,
|
|
272
|
+
back,
|
|
273
|
+
phase,
|
|
274
|
+
winner: winnerModel || '',
|
|
275
|
+
recut: recutNote || '',
|
|
276
|
+
racers: order.map((id) => {
|
|
277
|
+
const row = cells.get(id);
|
|
278
|
+
return {
|
|
279
|
+
model: id,
|
|
280
|
+
short: shortModelName(id),
|
|
281
|
+
status: row.status,
|
|
282
|
+
preview: row.status === 'failed' ? '' : clipRacePreview(row.preview),
|
|
283
|
+
fail: row.fail || '',
|
|
284
|
+
};
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
const emitRace = () => { onRace?.(snapshot()); };
|
|
165
288
|
return {
|
|
166
|
-
start() {
|
|
289
|
+
start(models, extra) {
|
|
290
|
+
if (Array.isArray(models)) {
|
|
291
|
+
for (const m of models) if (m) ensure(m);
|
|
292
|
+
}
|
|
293
|
+
if (extra && extra.recut) recutNote = String(extra.recut);
|
|
294
|
+
paintStatus();
|
|
295
|
+
emitRace();
|
|
296
|
+
},
|
|
297
|
+
snapshot,
|
|
167
298
|
liveModel() { return live; },
|
|
168
299
|
onToken(model, chunk) {
|
|
169
300
|
if (settled || chunk == null || chunk === '') return;
|
|
301
|
+
const row = ensure(model);
|
|
302
|
+
if (row.status === 'abandoned' || row.status === 'failed' || row.status === 'back') return;
|
|
170
303
|
buf.set(model, (buf.get(model) || '') + chunk);
|
|
304
|
+
row.preview = buf.get(model) || '';
|
|
305
|
+
if (row.status === 'waiting') row.status = 'streaming';
|
|
306
|
+
emitRace();
|
|
171
307
|
if (!live) {
|
|
172
308
|
live = model;
|
|
173
309
|
onDelta?.(chunk, { model });
|
|
@@ -175,8 +311,15 @@ export function createRaceFeed(onDelta, onStatus, need) {
|
|
|
175
311
|
}
|
|
176
312
|
if (live === model) onDelta?.(chunk, { model });
|
|
177
313
|
},
|
|
178
|
-
onFail(model) {
|
|
314
|
+
onFail(model, arrival) {
|
|
179
315
|
dead.add(model);
|
|
316
|
+
const row = ensure(model);
|
|
317
|
+
if (row.status !== 'abandoned' && row.status !== 'back') {
|
|
318
|
+
row.status = 'failed';
|
|
319
|
+
row.fail = raceFailKind(arrival || { model, text: '', error: 'error' });
|
|
320
|
+
row.preview = '';
|
|
321
|
+
emitRace();
|
|
322
|
+
}
|
|
180
323
|
if (settled || live !== model) return;
|
|
181
324
|
const next = [...buf.entries()].find(([m, t]) => m !== model && t && !dead.has(m));
|
|
182
325
|
if (next) {
|
|
@@ -186,18 +329,44 @@ export function createRaceFeed(onDelta, onStatus, need) {
|
|
|
186
329
|
live = null;
|
|
187
330
|
}
|
|
188
331
|
},
|
|
189
|
-
onBack() {
|
|
332
|
+
onBack(model) {
|
|
190
333
|
// Late countable stragglers after ship used to paint "racing 4/2 back…"
|
|
191
334
|
// onto an already-idle thread (GET /threads/:id returns raw liveStatus).
|
|
192
335
|
if (settled || back >= need) return;
|
|
193
336
|
back += 1;
|
|
337
|
+
let row = model ? ensure(model) : null;
|
|
338
|
+
if (!row) {
|
|
339
|
+
row = [...cells.values()].find((r) => r.status === 'streaming' || r.status === 'waiting');
|
|
340
|
+
}
|
|
341
|
+
if (row && row.status !== 'abandoned' && row.status !== 'failed') {
|
|
342
|
+
row.status = 'back';
|
|
343
|
+
if (buf.has(row.model)) row.preview = buf.get(row.model);
|
|
344
|
+
}
|
|
345
|
+
freezeStragglers();
|
|
194
346
|
paintStatus();
|
|
347
|
+
emitRace();
|
|
348
|
+
},
|
|
349
|
+
judge() {
|
|
350
|
+
if (settled) return;
|
|
351
|
+
phase = 'judging';
|
|
352
|
+
freezeStragglers();
|
|
353
|
+
emitRace();
|
|
195
354
|
},
|
|
196
355
|
settle(winner) {
|
|
197
356
|
settled = true;
|
|
357
|
+
phase = 'winner';
|
|
198
358
|
const text = String(winner?.text || '').trim()
|
|
199
359
|
? winner.text
|
|
200
360
|
: RACE_EVERY_FAILED;
|
|
361
|
+
winnerModel = winner?.error ? '' : (winner?.model || '');
|
|
362
|
+
if (winnerModel) {
|
|
363
|
+
const row = ensure(winnerModel);
|
|
364
|
+
if (row.status !== 'failed') {
|
|
365
|
+
row.status = 'back';
|
|
366
|
+
if (winner?.text) row.preview = String(winner.text);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
emitRace();
|
|
201
370
|
// Live stream already showing this answer — keep going, do not re-dump.
|
|
202
371
|
if (winner?.model && live === winner.model && !winner.error) return;
|
|
203
372
|
live = winner?.model || live;
|
package/lib/mcp.js
CHANGED
|
@@ -80,7 +80,7 @@ export function buildMcpServer() {
|
|
|
80
80
|
+ '(client-usable ceiling ~128M tokens; keep one call under ~9.8M) is bound once and the model reads only '
|
|
81
81
|
+ 'a few thousand tokens of it. Re-asking against an already-bound corpus is near-free and much faster, so '
|
|
82
82
|
+ 'send the corpus once and ask many questions. Returns the answer plus a payment receipt '
|
|
83
|
-
+ '(billedUsd,
|
|
83
|
+
+ '(billedUsd, directUsd, savedUsd, tokens actually read).',
|
|
84
84
|
inputSchema: {
|
|
85
85
|
prompt: z.string().describe('The question or instruction.'),
|
|
86
86
|
corpus: z.string().optional().describe('Optional big context body (document dump, logs, book...). Placed before the prompt.'),
|
|
@@ -128,6 +128,8 @@ export function buildMcpServer() {
|
|
|
128
128
|
receipt: receipt ? {
|
|
129
129
|
line: receipt.line,
|
|
130
130
|
billedUsd: receipt.billedUsd,
|
|
131
|
+
directUsd: receipt.directUsd,
|
|
132
|
+
savedUsd: receipt.savedUsd,
|
|
131
133
|
savesVsDirect: receipt.savesVsDirect,
|
|
132
134
|
pricing: receipt.pricing,
|
|
133
135
|
rail: receipt.rail,
|
package/lib/pay.js
CHANGED
|
@@ -413,6 +413,9 @@ export class PayClient {
|
|
|
413
413
|
line: receiptLine(accept, settle),
|
|
414
414
|
rail: railOf(accept),
|
|
415
415
|
billedUsd: accept.extra?.billedUsd,
|
|
416
|
+
directUsd: accept.extra?.directUsd,
|
|
417
|
+
savedUsd: accept.extra?.savedUsd,
|
|
418
|
+
cogsUsd: accept.extra?.cogsUsd,
|
|
416
419
|
savesVsDirect: accept.extra?.savesVsDirect,
|
|
417
420
|
pricing: accept.extra?.pricing,
|
|
418
421
|
symbol: accept.extra?.symbol,
|
package/lib/podagent.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './livestatus.js';
|
|
31
31
|
import {
|
|
32
32
|
probeGatewayRace, capRaceByCredit, inferRaceTier, RACE_NO_CREDIT,
|
|
33
|
+
recutRaceByHud, sessionDollarX,
|
|
33
34
|
} from './racesettle.js';
|
|
34
35
|
import { homedir } from 'node:os';
|
|
35
36
|
|
|
@@ -679,8 +680,16 @@ async function readProxySession(proxy = completionsProxy()) {
|
|
|
679
680
|
}
|
|
680
681
|
|
|
681
682
|
async function raceBudget(hooks) {
|
|
682
|
-
|
|
683
|
-
|
|
683
|
+
const injected = hooks.creditUsd != null || hooks.quoteUsd != null
|
|
684
|
+
|| hooks.spentUsd != null || hooks.directUsd != null || hooks.dollarX != null;
|
|
685
|
+
if (injected) {
|
|
686
|
+
return {
|
|
687
|
+
creditUsd: hooks.creditUsd,
|
|
688
|
+
quoteUsd: hooks.quoteUsd,
|
|
689
|
+
spentUsd: hooks.spentUsd,
|
|
690
|
+
directUsd: hooks.directUsd,
|
|
691
|
+
dollarX: hooks.dollarX,
|
|
692
|
+
};
|
|
684
693
|
}
|
|
685
694
|
// Injected stream = unit-test N-parallel path. Do not poke :8402.
|
|
686
695
|
if (hooks.stream) return {};
|
|
@@ -688,6 +697,8 @@ async function raceBudget(hooks) {
|
|
|
688
697
|
return {
|
|
689
698
|
creditUsd: s?.creditUsd,
|
|
690
699
|
quoteUsd: s?.lastQuoteUsd ?? s?.quoteUsd,
|
|
700
|
+
spentUsd: s?.spentUsd,
|
|
701
|
+
directUsd: s?.directUsd,
|
|
691
702
|
};
|
|
692
703
|
}
|
|
693
704
|
|
|
@@ -719,8 +730,8 @@ async function brainGatewayRace(messages, onDelta, contextId, models, need, maxT
|
|
|
719
730
|
const classify = hooks.classify || classifyRaceAnswer;
|
|
720
731
|
const pairwise = hooks.pairwise || pairwiseTied;
|
|
721
732
|
const minScore = hooks.minScore != null ? Number(hooks.minScore) : RACE_MIN_SCORE;
|
|
722
|
-
const feed = createRaceFeed(onDelta, onStatus, want);
|
|
723
|
-
feed.start();
|
|
733
|
+
const feed = createRaceFeed(onDelta, onStatus, want, hooks.onRace);
|
|
734
|
+
feed.start(models, hooks.raceRecut ? { recut: hooks.raceRecut } : undefined);
|
|
724
735
|
|
|
725
736
|
const arrivals = [];
|
|
726
737
|
const done = [];
|
|
@@ -772,7 +783,7 @@ async function brainGatewayRace(messages, onDelta, contextId, models, need, maxT
|
|
|
772
783
|
if (isRaceCountable(lastFail)) {
|
|
773
784
|
arrivals.push(lastFail);
|
|
774
785
|
done.push(lastFail);
|
|
775
|
-
feed.onBack();
|
|
786
|
+
feed.onBack(lastFail.model);
|
|
776
787
|
if (text) onDelta(text);
|
|
777
788
|
break;
|
|
778
789
|
}
|
|
@@ -785,9 +796,9 @@ async function brainGatewayRace(messages, onDelta, contextId, models, need, maxT
|
|
|
785
796
|
arrivals.push(a);
|
|
786
797
|
if (isRaceCountable(a)) {
|
|
787
798
|
done.push(a);
|
|
788
|
-
feed.onBack();
|
|
799
|
+
feed.onBack(a.model);
|
|
789
800
|
} else {
|
|
790
|
-
feed.onFail(a.model);
|
|
801
|
+
feed.onFail(a.model, a);
|
|
791
802
|
}
|
|
792
803
|
}
|
|
793
804
|
lastFail = parsed.arrivals[parsed.arrivals.length - 1] || lastFail;
|
|
@@ -804,6 +815,7 @@ async function brainGatewayRace(messages, onDelta, contextId, models, need, maxT
|
|
|
804
815
|
if (cands.length === 1) return ship(cands[0]);
|
|
805
816
|
|
|
806
817
|
onStatus?.('judging…');
|
|
818
|
+
feed.judge();
|
|
807
819
|
const scored = await Promise.all(cands.map(async (c) => {
|
|
808
820
|
let score = 0;
|
|
809
821
|
try { score = Number(await classify(messages, c)) || 0; } catch { score = 0; }
|
|
@@ -960,6 +972,31 @@ export async function brainRace(messages, onDelta, contextId, models, need = 1,
|
|
|
960
972
|
const minScore = hooks.minScore != null ? Number(hooks.minScore) : RACE_MIN_SCORE;
|
|
961
973
|
let list = (models || []).filter(Boolean).slice(0, RACE_MAX);
|
|
962
974
|
const budget = await raceBudget(hooks);
|
|
975
|
+
let tier = hooks.tier || inferRaceTier(list, 'medium');
|
|
976
|
+
let wantNeed = Math.max(1, Math.min(Number(need) || 1, list.length || 1));
|
|
977
|
+
const dollarX = sessionDollarX(budget);
|
|
978
|
+
const hud = recutRaceByHud({
|
|
979
|
+
y: list.length, need: wantNeed, dollarX, tier,
|
|
980
|
+
});
|
|
981
|
+
let raceRecut = '';
|
|
982
|
+
if (hud.recut) {
|
|
983
|
+
raceRecut = 'savings';
|
|
984
|
+
if (hud.tier !== tier && !hooks.stream) {
|
|
985
|
+
try {
|
|
986
|
+
const next = await tierModels(hud.tier, hud.y, true);
|
|
987
|
+
if (next.length) { list = next; tier = hud.tier; }
|
|
988
|
+
else list = list.slice(0, hud.y);
|
|
989
|
+
} catch { list = list.slice(0, hud.y); }
|
|
990
|
+
} else {
|
|
991
|
+
list = list.slice(0, hud.y);
|
|
992
|
+
tier = hud.tier;
|
|
993
|
+
}
|
|
994
|
+
wantNeed = Math.min(hud.need, list.length);
|
|
995
|
+
onStatus?.(hud.y < 2
|
|
996
|
+
? 'race recut to 1 — savings'
|
|
997
|
+
: `race recut to ${hud.y} — savings`);
|
|
998
|
+
}
|
|
999
|
+
hooks = { ...hooks, tier, raceRecut };
|
|
963
1000
|
const capped = capRaceByCredit(list.length, budget);
|
|
964
1001
|
if (capped.n < 1) {
|
|
965
1002
|
onStatus?.('race refused — no credit');
|
|
@@ -970,7 +1007,7 @@ export async function brainRace(messages, onDelta, contextId, models, need = 1,
|
|
|
970
1007
|
onStatus?.(capped.n < 2 ? 'race shrunk to 1 — credit' : `race shrunk to ${capped.n} — credit`);
|
|
971
1008
|
}
|
|
972
1009
|
if (list.length < 2) return stream(messages, onDelta, contextId, list[0], maxTokens, 0, 0, onStatus);
|
|
973
|
-
const want = Math.max(1, Math.min(
|
|
1010
|
+
const want = Math.max(1, Math.min(wantNeed, list.length));
|
|
974
1011
|
|
|
975
1012
|
// One Fly settle when the completions door honors `race:`. Custom stream
|
|
976
1013
|
// hooks (unit tests of the N-parallel judge) keep the old path. Old
|
|
@@ -985,8 +1022,8 @@ export async function brainRace(messages, onDelta, contextId, models, need = 1,
|
|
|
985
1022
|
return brainGatewayRace(messages, onDelta, contextId, list, want, maxTokens, onStatus, hooks);
|
|
986
1023
|
}
|
|
987
1024
|
|
|
988
|
-
const feed = createRaceFeed(onDelta, onStatus, want);
|
|
989
|
-
feed.start();
|
|
1025
|
+
const feed = createRaceFeed(onDelta, onStatus, want, hooks.onRace);
|
|
1026
|
+
feed.start(list, raceRecut ? { recut: raceRecut } : undefined);
|
|
990
1027
|
|
|
991
1028
|
const done = [];
|
|
992
1029
|
const arrivals = [];
|
|
@@ -1034,7 +1071,7 @@ export async function brainRace(messages, onDelta, contextId, models, need = 1,
|
|
|
1034
1071
|
if (isRaceCountable(last)) {
|
|
1035
1072
|
arrivals.push(last);
|
|
1036
1073
|
done.push(last);
|
|
1037
|
-
feed.onBack();
|
|
1074
|
+
feed.onBack(m);
|
|
1038
1075
|
return;
|
|
1039
1076
|
}
|
|
1040
1077
|
} catch (e) {
|
|
@@ -1043,7 +1080,7 @@ export async function brainRace(messages, onDelta, contextId, models, need = 1,
|
|
|
1043
1080
|
if (!shouldRetryRaceArrival(last) || attempt === 1 || raceAbort.signal.aborted) break;
|
|
1044
1081
|
}
|
|
1045
1082
|
arrivals.push(last);
|
|
1046
|
-
feed.onFail(m);
|
|
1083
|
+
feed.onFail(m, last);
|
|
1047
1084
|
};
|
|
1048
1085
|
|
|
1049
1086
|
const attempts = list.map((m) => runOne(m).finally(() => {
|
|
@@ -1068,6 +1105,7 @@ export async function brainRace(messages, onDelta, contextId, models, need = 1,
|
|
|
1068
1105
|
if (cands.length === 1) return ship(cands[0]);
|
|
1069
1106
|
|
|
1070
1107
|
onStatus?.('judging…');
|
|
1108
|
+
feed.judge();
|
|
1071
1109
|
const scored = await Promise.all(cands.map(async (c) => {
|
|
1072
1110
|
let score = 0;
|
|
1073
1111
|
try { score = Number(await classify(messages, c)) || 0; } catch { score = 0; }
|