mslxdff 0.1.87 → 0.1.89
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/bench/cline-bench.js +21 -33
- package/src/bench/probe.js +4 -11
- package/src/bench/runner.js +8 -23
- package/src/bench/via-probe.js +12 -30
- package/src/bench/workbuddy-bench.js +15 -31
- package/src/chat-pipeline/engine.js +241 -0
- package/src/chat-pipeline/index.js +86 -0
- package/src/chat-pipeline/planner.js +31 -0
- package/src/chat-pipeline/policy.js +73 -0
- package/src/providers/cline/chat.js +26 -53
- package/src/providers/workbuddy/chat.js +109 -221
- package/src/routes/chat/gateway.js +23 -286
- package/src/routes/groups-relay.js +85 -1
- package/src/routes/index.js +7 -1
- package/src/routes/relay-queue.js +53 -0
- package/src/runtime/bootstrap.js +116 -34
- package/src/transport/index.js +248 -0
- package/src/transport/pool.js +60 -0
- package/src/transport/retry.js +24 -0
- package/src/transport/sse.js +93 -0
- package/src/upstream.js +113 -287
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { joinUrl } from "../base.js";
|
|
2
2
|
import { isAuthError, isInsufficientStatus } from "./auth.js";
|
|
3
3
|
import { appendRotationLog as defaultAppend } from "./rotation-log.js";
|
|
4
|
+
import { createTransport } from "../../transport/index.js";
|
|
4
5
|
|
|
5
6
|
function buildAuthHeaders(key, auth) {
|
|
6
7
|
const h = {
|
|
@@ -13,71 +14,62 @@ function buildAuthHeaders(key, auth) {
|
|
|
13
14
|
};
|
|
14
15
|
if (key) h["Authorization"] = `Bearer ${key}`;
|
|
15
16
|
if (auth?.uid) h["X-User-Id"] = auth.uid;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (auth?.enterpriseId) {
|
|
19
|
-
h["X-Enterprise-Id"] = auth.enterpriseId;
|
|
20
|
-
h["X-Tenant-Id"] = auth.enterpriseId;
|
|
21
|
-
}
|
|
17
|
+
h["X-Domain"] = auth?.domain || "www.codebuddy.cn";
|
|
18
|
+
if (auth?.enterpriseId) { h["X-Enterprise-Id"] = auth.enterpriseId; h["X-Tenant-Id"] = auth.enterpriseId; }
|
|
22
19
|
return h;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
function withUidHeader(res, uid) {
|
|
26
|
-
try {
|
|
27
|
-
|
|
28
|
-
return res;
|
|
29
|
-
} catch {
|
|
23
|
+
try { res.headers.set("x-mslxdff-workbuddy-uid", uid); return res; }
|
|
24
|
+
catch {
|
|
30
25
|
try {
|
|
31
26
|
const h = new Headers(res.headers);
|
|
32
27
|
h.set("x-mslxdff-workbuddy-uid", uid);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
} catch {
|
|
37
|
-
return res;
|
|
38
|
-
}
|
|
28
|
+
const c = new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
29
|
+
c._t = res._t;
|
|
30
|
+
return c;
|
|
31
|
+
} catch { return res; }
|
|
39
32
|
}
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
function
|
|
35
|
+
function nowMs(clock) { return typeof performance !== "undefined" && performance.now ? performance.now() : clock(); }
|
|
36
|
+
|
|
37
|
+
function errRes(status, msg, reason, uid, t0, clock) {
|
|
38
|
+
const r = new Response(JSON.stringify({ error: msg }), { status, headers: { "Content-Type": "application/json", "x-mslxdff-workbuddy-reason": reason, "x-mslxdff-workbuddy-uid": uid } });
|
|
39
|
+
r._t = { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - t0) };
|
|
40
|
+
return r;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function failErr(msg, t0, clock, extra) {
|
|
44
|
+
const e = new Error(msg);
|
|
45
|
+
e._t = { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - t0), ...(extra || {}) };
|
|
46
|
+
return e;
|
|
47
|
+
}
|
|
43
48
|
|
|
44
49
|
export function createChatService({
|
|
45
50
|
id = "workbuddy",
|
|
46
51
|
baseUrl,
|
|
47
52
|
chatPath,
|
|
48
|
-
keys,
|
|
49
|
-
authList,
|
|
50
|
-
ring,
|
|
53
|
+
keys,
|
|
54
|
+
authList,
|
|
55
|
+
ring,
|
|
51
56
|
fetchImpl,
|
|
52
57
|
dispatcher,
|
|
53
58
|
connectTimeoutMs = 30_000,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
502: { attempts: 1, delayMs: 100 },
|
|
58
|
-
503: { attempts: 1, delayMs: 100 },
|
|
59
|
-
504: { attempts: 1, delayMs: 100 },
|
|
60
|
-
},
|
|
61
|
-
balanceCache, // { getCachedBalance, setCachedBalance }
|
|
62
|
-
authService, // { refreshTokenFor, maybeProactiveRefresh }
|
|
63
|
-
logger, // { append } or null
|
|
59
|
+
balanceCache,
|
|
60
|
+
authService,
|
|
61
|
+
logger,
|
|
64
62
|
clock = Date.now,
|
|
65
63
|
cooldownMs = 30_000,
|
|
66
64
|
} = {}) {
|
|
67
65
|
const getBalance = balanceCache?.getCachedBalance || (() => null);
|
|
68
66
|
const setBalance = balanceCache?.setCachedBalance || (() => {});
|
|
69
67
|
const doLog = (opts) => {
|
|
70
|
-
if (logger && typeof logger.append === "function") {
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (logger && typeof logger.log === "function") {
|
|
75
|
-
try { logger.log(opts); } catch {}
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
// fallback to default append (may write to fs; test passes noop logger so not called)
|
|
68
|
+
if (logger && typeof logger.append === "function") { try { logger.append(opts); } catch {} return; }
|
|
69
|
+
if (logger && typeof logger.log === "function") { try { logger.log(opts); } catch {} return; }
|
|
79
70
|
try { defaultAppend(opts); } catch {}
|
|
80
71
|
};
|
|
72
|
+
const transport = createTransport({ fetchImpl, dispatcher, keepAlive: !!dispatcher, timeoutMs: connectTimeoutMs, retry: {} });
|
|
81
73
|
|
|
82
74
|
function authForKey(key) {
|
|
83
75
|
const idx = keys.indexOf(key);
|
|
@@ -86,113 +78,77 @@ export function createChatService({
|
|
|
86
78
|
return { uid: "", domain: "www.codebuddy.cn", enterpriseId: "", refreshToken: "" };
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
async function
|
|
90
|
-
|
|
91
|
-
const timer = setTimeout(() => controller.abort(new Error(`${id} timed out after ${connectTimeoutMs}ms`)), connectTimeoutMs);
|
|
92
|
-
try {
|
|
93
|
-
const auth = authForKey(key);
|
|
94
|
-
const headers = buildAuthHeaders(key, auth);
|
|
95
|
-
const finalBody = { ...body, stream: true };
|
|
96
|
-
const opts = { method: "POST", headers, body: JSON.stringify(finalBody), signal: controller.signal };
|
|
97
|
-
if (dispatcher) opts.dispatcher = dispatcher;
|
|
98
|
-
const res = await fetchImpl(url, opts);
|
|
99
|
-
return res;
|
|
100
|
-
} catch (err) {
|
|
101
|
-
return err;
|
|
102
|
-
} finally {
|
|
103
|
-
clearTimeout(timer);
|
|
104
|
-
}
|
|
81
|
+
async function fetchOnce(url, body, key, auth) {
|
|
82
|
+
return transport.request({ url, headers: buildAuthHeaders(key, auth), body: { ...body, stream: true }, stream: true });
|
|
105
83
|
}
|
|
106
84
|
|
|
85
|
+
async function withRefresh(url, body, key, auth) {
|
|
86
|
+
let res;
|
|
87
|
+
try { res = await fetchOnce(url, body, key, auth); }
|
|
88
|
+
catch (e) { throw e; }
|
|
89
|
+
if (res.status < 400) return res;
|
|
90
|
+
let txt = "";
|
|
91
|
+
try { txt = await res.text(); } catch {}
|
|
92
|
+
if (!isAuthError(res.status, txt)) return res;
|
|
93
|
+
const newKey = await authService?.refreshTokenFor?.(key, auth);
|
|
94
|
+
if (!newKey) throw failErr(`workbuddy refresh failed for ${auth?.uid || ""}: ${txt.slice(0, 120)}`, _t0(), clock);
|
|
95
|
+
const auth2 = authForKey(newKey);
|
|
96
|
+
let res2;
|
|
97
|
+
try { res2 = await fetchOnce(url, body, newKey, auth2); }
|
|
98
|
+
catch (e2) { throw e2; }
|
|
99
|
+
let stillTxt = "";
|
|
100
|
+
if (res2.status >= 400) { try { stillTxt = await res2.text(); } catch {} }
|
|
101
|
+
const stillAuth = isAuthError(res2.status, stillTxt);
|
|
102
|
+
const uid2 = auth2.uid || auth.uid;
|
|
103
|
+
res2 = withUidHeader(res2, uid2);
|
|
104
|
+
res2._t = res2._t || { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - _t0()) };
|
|
105
|
+
if (stillAuth || res2.status === 429 || res2.status >= 500) try { ring.onError(newKey); } catch {}
|
|
106
|
+
doLog({ uid: uid2, model: _modelForLog(), totalMs: Math.round(nowMs(clock) - _t0()), balanceHit: false, error: stillAuth ? `still auth ${res2.status}` : undefined });
|
|
107
|
+
if (stillAuth) throw failErr(`workbuddy auth still failing after refresh for ${uid2}: ${stillTxt.slice(0, 120)}`, _t0(), clock);
|
|
108
|
+
return res2;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let _t0Val = 0;
|
|
112
|
+
let _modelVal = "";
|
|
113
|
+
function _t0() { return _t0Val; }
|
|
114
|
+
function _modelForLog() { return _modelVal; }
|
|
115
|
+
|
|
107
116
|
async function runChat(body, activeRing, opts = {}) {
|
|
108
117
|
const url = joinUrl(baseUrl, chatPath);
|
|
109
|
-
const t0 =
|
|
118
|
+
const t0 = nowMs(clock);
|
|
119
|
+
_t0Val = t0;
|
|
120
|
+
_modelVal = body?.model || "";
|
|
110
121
|
const preferredUid = opts?.workbuddyUid ? String(opts.workbuddyUid).trim() : "";
|
|
111
|
-
const modelForLog = body?.model || "";
|
|
112
122
|
|
|
113
123
|
if (preferredUid) {
|
|
114
|
-
const idx = authList.findIndex(a => a.uid === preferredUid || a.uid.startsWith(preferredUid));
|
|
115
|
-
if (idx < 0) {
|
|
116
|
-
const errBody = JSON.stringify({ error: `workbuddy uid not found: ${preferredUid}` });
|
|
117
|
-
const res = new Response(errBody, { status: 403, headers: { "Content-Type": "application/json", "x-mslxdff-workbuddy-reason": "uid-not-found", "x-mslxdff-workbuddy-uid": preferredUid } });
|
|
118
|
-
res._t = { attempts: [], waitMs: 0, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
119
|
-
return res;
|
|
120
|
-
}
|
|
124
|
+
const idx = authList.findIndex((a) => a.uid === preferredUid || String(a.uid).startsWith(preferredUid));
|
|
125
|
+
if (idx < 0) return errRes(403, `workbuddy uid not found: ${preferredUid}`, "uid-not-found", preferredUid, t0, clock);
|
|
121
126
|
const key = keys[idx];
|
|
122
127
|
const auth = authList[idx];
|
|
123
128
|
authService?.maybeProactiveRefresh?.(auth, key);
|
|
124
129
|
const cached = getBalance(auth.uid);
|
|
125
|
-
if (cached && Number(cached.total) === 0) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
res
|
|
130
|
+
if (cached && Number(cached.total) === 0) return errRes(403, `workbuddy uid in cooldown (balance 0): ${auth.uid}`, "uid-cooling", auth.uid, t0, clock);
|
|
131
|
+
try {
|
|
132
|
+
let res = await withRefresh(url, body, key, auth);
|
|
133
|
+
res = withUidHeader(res, auth.uid);
|
|
134
|
+
res._t = res._t || { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - t0) };
|
|
135
|
+
if (res.status === 401 || res.status === 403 || res.status === 429 || res.status >= 500) try { activeRing.onError(key); } catch {}
|
|
136
|
+
doLog({ uid: auth.uid, model: _modelVal, totalMs: Math.round(nowMs(clock) - t0), balanceHit: false });
|
|
129
137
|
return res;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const t = typeof performance !== "undefined" && performance.now ? performance.now() : clock();
|
|
135
|
-
let result = await attemptOnce(url, body, key);
|
|
136
|
-
const type = result instanceof Error ? "network" : `http${result?.status}`;
|
|
137
|
-
attempts.push({ attempt, type, ms: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t) });
|
|
138
|
-
if (result instanceof Error) {
|
|
139
|
-
const entry = retry?.network;
|
|
140
|
-
if (entry && attempt < entry.attempts) { await sleep(entry.delayMs); waitMs += entry.delayMs; continue; }
|
|
141
|
-
activeRing.onError(key);
|
|
142
|
-
result._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
143
|
-
throw result;
|
|
144
|
-
}
|
|
145
|
-
if (attempt === 0) {
|
|
146
|
-
let bodyText = "";
|
|
147
|
-
try { bodyText = await result.clone().text(); } catch {}
|
|
148
|
-
if (isAuthError(result.status, bodyText)) {
|
|
149
|
-
const newKey = await authService?.refreshTokenFor?.(key, auth);
|
|
150
|
-
if (newKey) {
|
|
151
|
-
const auth2 = authForKey(newKey);
|
|
152
|
-
const headers2 = buildAuthHeaders(newKey, auth2);
|
|
153
|
-
const finalBody = { ...body, stream: true };
|
|
154
|
-
const controller2 = new AbortController();
|
|
155
|
-
const timer2 = setTimeout(() => controller2.abort(new Error(`${id} timed out after ${connectTimeoutMs}ms`)), connectTimeoutMs);
|
|
156
|
-
try {
|
|
157
|
-
const opts2 = { method: "POST", headers: headers2, body: JSON.stringify(finalBody), signal: controller2.signal };
|
|
158
|
-
if (dispatcher) opts2.dispatcher = dispatcher;
|
|
159
|
-
let res2 = await fetchImpl(url, opts2);
|
|
160
|
-
let res2Body = "";
|
|
161
|
-
try { res2Body = await res2.clone().text(); } catch {}
|
|
162
|
-
const stillAuth = isAuthError(res2.status, res2Body);
|
|
163
|
-
res2._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0), refreshed: true };
|
|
164
|
-
res2 = withUidHeader(res2, auth2.uid || auth.uid);
|
|
165
|
-
if (stillAuth || res2.status === 429 || res2.status >= 500) activeRing.onError(newKey);
|
|
166
|
-
doLog({ uid: auth2.uid || auth.uid, model: modelForLog, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0), balanceHit: false, error: stillAuth ? `still auth ${res2.status}` : undefined });
|
|
167
|
-
return res2;
|
|
168
|
-
} catch (e2) { e2._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) }; throw e2; } finally { clearTimeout(timer2); }
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const entry = retry?.[result.status];
|
|
173
|
-
if (entry && attempt < entry.attempts) { await sleep(entry.delayMs); waitMs += entry.delayMs; continue; }
|
|
174
|
-
if (result.status === 401 || result.status === 403 || result.status === 429 || result.status >= 500) activeRing.onError(key);
|
|
175
|
-
result = withUidHeader(result, auth.uid);
|
|
176
|
-
result._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
177
|
-
doLog({ uid: auth.uid, model: modelForLog, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0), balanceHit: false });
|
|
178
|
-
return result;
|
|
138
|
+
} catch (e) {
|
|
139
|
+
e._t = e._t || { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - t0) };
|
|
140
|
+
if (!(e instanceof Error) || !String(e.message).includes("refresh failed")) try { activeRing.onError(key); } catch {}
|
|
141
|
+
throw e;
|
|
179
142
|
}
|
|
180
143
|
}
|
|
181
144
|
|
|
182
|
-
// auto rotation
|
|
183
|
-
const attempts = [];
|
|
184
|
-
let waitMs = 0;
|
|
185
|
-
let lastErr = null;
|
|
186
145
|
const tried = new Set();
|
|
187
146
|
const maxTries = Math.max(1, keys.length);
|
|
147
|
+
let lastErr = null;
|
|
188
148
|
for (let triedCount = 0; triedCount < maxTries; triedCount++) {
|
|
189
149
|
const key = activeRing.next();
|
|
190
150
|
if (!key) {
|
|
191
|
-
if (triedCount === 0) {
|
|
192
|
-
const err = new Error(`${id}: all API keys are in cooldown (last error < ${cooldownMs}ms ago) — provider temporarily unavailable`);
|
|
193
|
-
err._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0), cooldownMs };
|
|
194
|
-
throw err;
|
|
195
|
-
}
|
|
151
|
+
if (triedCount === 0) throw failErr(`${id}: all API keys are in cooldown (last error < ${cooldownMs}ms ago) — provider temporarily unavailable`, t0, clock, { cooldownMs });
|
|
196
152
|
break;
|
|
197
153
|
}
|
|
198
154
|
const auth = authForKey(key);
|
|
@@ -201,110 +157,42 @@ export function createChatService({
|
|
|
201
157
|
tried.add(uid || key);
|
|
202
158
|
const cached = uid ? getBalance(uid) : null;
|
|
203
159
|
if (cached && Number(cached.total) === 0) {
|
|
204
|
-
activeRing.onError(key);
|
|
160
|
+
try { activeRing.onError(key); } catch {}
|
|
205
161
|
setBalance(uid, { ...cached, total: 0 });
|
|
206
|
-
doLog({ uid, model:
|
|
207
|
-
|
|
208
|
-
if (keys.length === 1 || activeRing.available() === 0) {
|
|
209
|
-
const errBody = JSON.stringify({ error: `workbuddy uid in cooldown (balance 0): ${uid}` });
|
|
210
|
-
const res = new Response(errBody, { status: 403, headers: { "Content-Type": "application/json", "x-mslxdff-workbuddy-reason": "uid-cooling", "x-mslxdff-workbuddy-uid": uid } });
|
|
211
|
-
res._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
212
|
-
return res;
|
|
213
|
-
}
|
|
162
|
+
doLog({ uid, model: _modelVal, totalMs: Math.round(nowMs(clock) - t0), balanceHit: true });
|
|
163
|
+
if (keys.length === 1 || activeRing.available() === 0) return errRes(403, `workbuddy uid in cooldown (balance 0): ${uid}`, "uid-cooling", uid, t0, clock);
|
|
214
164
|
continue;
|
|
215
165
|
}
|
|
216
|
-
if (!key && activeRing.size === 0) {
|
|
217
|
-
const err = new Error(`${id}: missing MSLXDFF_WORKBUDDY_KEY (chat requires a real key) — run node workbuddy-token-auto.js`);
|
|
218
|
-
err._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
219
|
-
throw err;
|
|
220
|
-
}
|
|
166
|
+
if (!key && activeRing.size === 0) throw failErr(`${id}: missing MSLXDFF_WORKBUDDY_KEY (chat requires a real key) — run node workbuddy-token-auto.js`, t0, clock);
|
|
221
167
|
authService?.maybeProactiveRefresh?.(auth, key);
|
|
222
|
-
let
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
attempts.push({ attempt: triedCount * 10 + attempt, type, ms: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t), uid });
|
|
229
|
-
if (result instanceof Error) {
|
|
230
|
-
const entry = retry?.network;
|
|
231
|
-
if (entry && attempt < entry.attempts) { await sleep(entry.delayMs); waitMs += entry.delayMs; continue; }
|
|
232
|
-
activeRing.onError(key);
|
|
233
|
-
lastErr = result;
|
|
234
|
-
result._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
if (attempt === 0) {
|
|
238
|
-
let bodyText = "";
|
|
239
|
-
try { bodyText = await result.clone().text(); } catch {}
|
|
240
|
-
const needRefresh = isAuthError(result.status, bodyText);
|
|
241
|
-
if (needRefresh) {
|
|
242
|
-
const newKey = await authService?.refreshTokenFor?.(key, auth);
|
|
243
|
-
if (newKey) {
|
|
244
|
-
const auth2 = authForKey(newKey);
|
|
245
|
-
const headers2 = buildAuthHeaders(newKey, auth2);
|
|
246
|
-
const finalBody = { ...body, stream: true };
|
|
247
|
-
const controller2 = new AbortController();
|
|
248
|
-
const timer2 = setTimeout(() => controller2.abort(new Error(`${id} timed out after ${connectTimeoutMs}ms`)), connectTimeoutMs);
|
|
249
|
-
try {
|
|
250
|
-
const opts2 = { method: "POST", headers: headers2, body: JSON.stringify(finalBody), signal: controller2.signal };
|
|
251
|
-
if (dispatcher) opts2.dispatcher = dispatcher;
|
|
252
|
-
let res2 = await fetchImpl(url, opts2);
|
|
253
|
-
let res2Body = "";
|
|
254
|
-
try { res2Body = await res2.clone().text(); } catch {}
|
|
255
|
-
const stillAuth = isAuthError(res2.status, res2Body);
|
|
256
|
-
res2._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0), refreshed: true };
|
|
257
|
-
res2 = withUidHeader(res2, auth2.uid || uid);
|
|
258
|
-
if (stillAuth || res2.status === 429 || res2.status >= 500) activeRing.onError(newKey);
|
|
259
|
-
doLog({ uid: auth2.uid || uid, model: modelForLog, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0), balanceHit: false, error: stillAuth ? `still auth ${res2.status}` : undefined });
|
|
260
|
-
if (stillAuth) {
|
|
261
|
-
lastErr = new Error(`workbuddy auth still failing after refresh for ${uid}: ${res2Body.slice(0, 120)}`);
|
|
262
|
-
lastErr._t = res2._t;
|
|
263
|
-
activeRing.onError(newKey);
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
return res2;
|
|
267
|
-
} catch (e2) { e2._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) }; lastErr = e2; break; } finally { clearTimeout(timer2); }
|
|
268
|
-
} else {
|
|
269
|
-
lastErr = new Error(`workbuddy refresh failed for ${uid}: ${bodyText.slice(0, 120)}`);
|
|
270
|
-
lastErr._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
271
|
-
activeRing.onError(key);
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
const entry = retry?.[result.status];
|
|
277
|
-
if (entry && attempt < entry.attempts) { await sleep(entry.delayMs); waitMs += entry.delayMs; continue; }
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
if (result instanceof Error) {
|
|
168
|
+
let res;
|
|
169
|
+
try { res = await withRefresh(url, body, key, auth); }
|
|
170
|
+
catch (e) {
|
|
171
|
+
e._t = e._t || { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - t0) };
|
|
172
|
+
try { activeRing.onError(key); } catch {}
|
|
173
|
+
lastErr = e;
|
|
281
174
|
continue;
|
|
282
175
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
lastErr = new Error(`workbuddy insufficient for ${uid}: ${bodyText.slice(0, 120)}`);
|
|
294
|
-
lastErr._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
295
|
-
continue;
|
|
176
|
+
if (res.status === 402 || res.status === 403 || res.status === 429) {
|
|
177
|
+
let txt = "";
|
|
178
|
+
try { txt = await res.text(); } catch {}
|
|
179
|
+
if (isInsufficientStatus(res.status, txt, cached)) {
|
|
180
|
+
try { activeRing.onError(key); } catch {}
|
|
181
|
+
if (uid) setBalance(uid, { total: 0, dailyPacks: 0, activeCount: 0, nextExpire: null, fetchedAt: clock() });
|
|
182
|
+
doLog({ uid, model: _modelVal, totalMs: Math.round(nowMs(clock) - t0), balanceHit: true, error: txt.slice(0, 120) });
|
|
183
|
+
lastErr = failErr(`workbuddy insufficient for ${uid}: ${txt.slice(0, 120)}`, t0, clock);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
296
186
|
}
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
doLog({ uid, model:
|
|
301
|
-
return
|
|
187
|
+
if (res.status === 401 || res.status === 403 || res.status === 429 || res.status >= 500) try { activeRing.onError(key); } catch {}
|
|
188
|
+
res = withUidHeader(res, uid);
|
|
189
|
+
res._t = res._t || { attempts: [], waitMs: 0, totalMs: Math.round(nowMs(clock) - t0) };
|
|
190
|
+
doLog({ uid, model: _modelVal, totalMs: Math.round(nowMs(clock) - t0), balanceHit: false });
|
|
191
|
+
return res;
|
|
302
192
|
}
|
|
303
193
|
if (lastErr) throw lastErr;
|
|
304
|
-
|
|
305
|
-
err._t = { attempts, waitMs, totalMs: Math.round((typeof performance !== "undefined" && performance.now ? performance.now() : clock()) - t0) };
|
|
306
|
-
throw err;
|
|
194
|
+
throw failErr(`${id}: all workbuddy accounts exhausted or unavailable`, t0, clock);
|
|
307
195
|
}
|
|
308
196
|
|
|
309
|
-
return { runChat, authForKey, buildAuthHeaders, withUidHeader, attemptOnce };
|
|
197
|
+
return { runChat, authForKey, buildAuthHeaders, withUidHeader, attemptOnce: fetchOnce };
|
|
310
198
|
}
|