mslxdff 0.1.64 → 0.1.66
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/bin/mslxdff.js +3 -3122
- package/package.json +1 -1
- package/src/chat/prompt.js +7 -2
- package/src/chat/repl.js +8 -4
- package/src/chat/upstream.js +271 -36
- package/src/cli/bootstrap.js +408 -0
- package/src/cli/commands/daemon.js +151 -0
- package/src/cli/commands/group.js +272 -0
- package/src/cli/commands/model.js +405 -0
- package/src/cli/commands/provider/add.js +106 -0
- package/src/cli/commands/provider/allowlist.js +99 -0
- package/src/cli/commands/provider/config.js +82 -0
- package/src/cli/commands/provider/index.js +193 -0
- package/src/cli/commands/provider/keys.js +135 -0
- package/src/cli/commands/provider/models.js +99 -0
- package/src/cli/commands/provider.js +1 -0
- package/src/cli/commands/sync.js +143 -0
- package/src/cli/commands/system.js +236 -0
- package/src/cli/commands/workbuddy.js +91 -0
- package/src/cli/format.js +119 -0
- package/src/cli/group-helpers.js +69 -0
- package/src/cli/help.js +66 -0
- package/src/cli/index.js +59 -0
- package/src/cli/interactive.js +84 -0
- package/src/cli/policy.js +119 -0
- package/src/cli/status.js +332 -0
- package/src/cli/util.js +24 -0
- package/src/models.js +15 -5
- package/src/providers/base.js +159 -0
- package/src/providers/generic.js +26 -166
- package/src/providers/keyring.js +19 -1
- package/src/providers/openrouter.js +32 -162
- package/src/providers/workbuddy.js +200 -131
- package/src/routes/chat/gateway.js +301 -0
- package/src/routes/chat/index.js +1 -239
- package/src/state/facade.js +57 -0
- package/src/state/memory.js +180 -0
- package/src/state/persist.js +42 -0
- package/src/state/schemas/allowlist.js +87 -0
- package/src/state/schemas/group.js +31 -0
- package/src/state/schemas/model.js +53 -0
- package/src/state/schemas/peer.js +31 -0
- package/src/state/schemas/port.js +10 -0
- package/src/state/schemas/provider.js +254 -0
- package/src/state/schemas/token.js +43 -0
- package/src/state/store.js +202 -0
- package/src/state.js +1 -711
|
@@ -6,25 +6,8 @@ import { join, dirname } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { getCachedBalance, setCachedBalance } from "./workbuddy-balance.js";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
const mod = await import("undici");
|
|
13
|
-
UndiciAgent = mod.Agent;
|
|
14
|
-
UndiciFetch = mod.fetch;
|
|
15
|
-
} catch {}
|
|
16
|
-
|
|
17
|
-
function joinUrl(base, path) {
|
|
18
|
-
const b = String(base || "").trim().replace(/\/+$/, "");
|
|
19
|
-
const p = String(path || "").trim();
|
|
20
|
-
if (!p) return b;
|
|
21
|
-
const pp = p.startsWith("/") ? p : `/${p}`;
|
|
22
|
-
return `${b}${pp}`;
|
|
23
|
-
}
|
|
24
|
-
function envInt(name, fallback) {
|
|
25
|
-
const v = Number(process.env[name]);
|
|
26
|
-
return Number.isInteger(v) && v > 0 ? v : fallback;
|
|
27
|
-
}
|
|
9
|
+
import { envInt, joinUrl, getUndici, createAgent } from "./base.js";
|
|
10
|
+
const { UndiciAgent, UndiciFetch } = getUndici();
|
|
28
11
|
function isTestEnv() {
|
|
29
12
|
if (process.env.NODE_ENV === "test") return true;
|
|
30
13
|
if (process.env.MSLXDFF_STATE_FILE && String(process.env.MSLXDFF_STATE_FILE).includes("mslxdff-test")) return true;
|
|
@@ -71,6 +54,30 @@ function isInsufficientStatus(status, bodyText, cached) {
|
|
|
71
54
|
return false;
|
|
72
55
|
}
|
|
73
56
|
|
|
57
|
+
// 401/403 或 body 含 token 失效关键词 → 视为需刷新(workbuddy 返回 200+JSON 或 SSE 错误时也能命中)
|
|
58
|
+
function isAuthError(status, bodyText) {
|
|
59
|
+
if (status === 401 || status === 403) return true;
|
|
60
|
+
const t = String(bodyText || "").toLowerCase();
|
|
61
|
+
// 常见 workbuddy 鉴权失败文案
|
|
62
|
+
if (t.includes("unauthorized") || t.includes("authenticate") || t.includes("invalid token") || t.includes("token expired") || t.includes("token invalid") || t.includes("access token") || t.includes("login expired") || t.includes("need login") || t.includes("session expired")) return true;
|
|
63
|
+
if (t.includes("code") && (t.includes("401") || t.includes("403")) && t.includes("token")) return true;
|
|
64
|
+
// JWT 失效的 400 也可能带 token
|
|
65
|
+
if (status === 400 && t.includes("token")) return true;
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function decodeJwtExp(token) {
|
|
70
|
+
try {
|
|
71
|
+
const payload = String(token || "").split(".")[1];
|
|
72
|
+
if (!payload) return 0;
|
|
73
|
+
const json = JSON.parse(Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
|
|
74
|
+
return Number(json.exp || 0);
|
|
75
|
+
} catch { return 0; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 并发去重:同一 uid 同时只刷一次
|
|
79
|
+
const inflightRefresh = new Map();
|
|
80
|
+
|
|
74
81
|
function withUidHeader(res, uid) {
|
|
75
82
|
try {
|
|
76
83
|
// try mutable set first
|
|
@@ -184,21 +191,13 @@ export function createWorkbuddyProvider({
|
|
|
184
191
|
} catch {}
|
|
185
192
|
}
|
|
186
193
|
|
|
187
|
-
|
|
194
|
+
let ring = createKeyRing(keys, { cooldownMs });
|
|
188
195
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
keepAliveTimeout: envInt("MSLXDFF_WORKBUDDY_KEEPALIVE_TIMEOUT", 30_000),
|
|
195
|
-
keepAliveMaxTimeout: envInt("MSLXDFF_WORKBUDDY_KEEPALIVE_MAX_TIMEOUT", 60_000),
|
|
196
|
-
connections: envInt("MSLXDFF_WORKBUDDY_KEEPALIVE_CONNECTIONS", 20),
|
|
197
|
-
pipelining: 1,
|
|
198
|
-
});
|
|
199
|
-
dispatcher = agent;
|
|
200
|
-
} catch {}
|
|
201
|
-
}
|
|
196
|
+
const { agent, dispatcher } = createAgent({
|
|
197
|
+
keepAliveTimeout: envInt("MSLXDFF_WORKBUDDY_KEEPALIVE_TIMEOUT", 30_000),
|
|
198
|
+
keepAliveMaxTimeout: envInt("MSLXDFF_WORKBUDDY_KEEPALIVE_MAX_TIMEOUT", 60_000),
|
|
199
|
+
connections: envInt("MSLXDFF_WORKBUDDY_KEEPALIVE_CONNECTIONS", 20),
|
|
200
|
+
});
|
|
202
201
|
|
|
203
202
|
function authForKey(key) {
|
|
204
203
|
const idx = keys.indexOf(key);
|
|
@@ -212,51 +211,76 @@ export function createWorkbuddyProvider({
|
|
|
212
211
|
const rt = auth?.refreshToken;
|
|
213
212
|
const uid = auth?.uid;
|
|
214
213
|
if (!rt || !uid) return null;
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
"
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
214
|
+
const dedupKey = String(uid);
|
|
215
|
+
if (inflightRefresh.has(dedupKey)) {
|
|
216
|
+
try { return await inflightRefresh.get(dedupKey); } catch { return null; }
|
|
217
|
+
}
|
|
218
|
+
const p = (async () => {
|
|
219
|
+
const url = joinUrl(resolvedBase, "/v2/plugin/auth/token/refresh");
|
|
220
|
+
const headers = {
|
|
221
|
+
"Content-Type": "application/json",
|
|
222
|
+
Authorization: `Bearer ${key}`,
|
|
223
|
+
"X-Refresh-Token": rt,
|
|
224
|
+
"X-User-Id": uid,
|
|
225
|
+
"X-Domain": auth.domain || "www.codebuddy.cn",
|
|
226
|
+
"User-Agent": "CLI/2.115.0 WorkBuddy/2.115.0",
|
|
227
|
+
Origin: "https://www.codebuddy.cn",
|
|
228
|
+
Referer: "https://www.codebuddy.cn/",
|
|
229
|
+
};
|
|
230
|
+
try {
|
|
231
|
+
const opts = { method: "POST", headers, body: "{}" };
|
|
232
|
+
if (dispatcher) opts.dispatcher = dispatcher;
|
|
233
|
+
const res = await fetchImpl(url, opts);
|
|
234
|
+
const text = await res.text();
|
|
235
|
+
const j = JSON.parse(text);
|
|
236
|
+
if (j.code === 0 && j.data?.accessToken) {
|
|
237
|
+
const newAt = j.data.accessToken;
|
|
238
|
+
const newRt = j.data.refreshToken || rt;
|
|
239
|
+
const idx = keys.indexOf(key);
|
|
240
|
+
if (idx >= 0) {
|
|
241
|
+
keys[idx] = newAt;
|
|
242
|
+
authList[idx] = { ...auth, refreshToken: newRt };
|
|
243
|
+
// 同步 ring,避免下一轮仍取旧 token
|
|
244
|
+
try { ring.replace(key, newAt); } catch {}
|
|
245
|
+
try { saveProviderConfig(id, { baseUrl: resolvedBase, keys: [...keys], auths: [...authList] }, file ? { file } : {}); } catch {}
|
|
246
|
+
try {
|
|
247
|
+
const authDir = process.env.WORKBUDDY_AUTH_DIR || (isTestEnv() ? join(tmpdir(), "mslxdff-test-auths") : (file && String(file).includes("mslxdff-") ? join(dirname(String(file)), "auths") : join(process.cwd(), "auths")));
|
|
248
|
+
mkdirSync(authDir, { recursive: true });
|
|
249
|
+
const expAt = (() => { try { return JSON.parse(Buffer.from(newAt.split(".")[1], "base64").toString()).exp; } catch { return Math.floor(Date.now()/1000)+5184000; } })();
|
|
250
|
+
const doc = { account: { uid, enterpriseId: auth.enterpriseId || "", nickname: "" }, auth: { accessToken: newAt, refreshToken: newRt, expiresAt: expAt, domain: auth.domain || "www.codebuddy.cn" } };
|
|
251
|
+
const fp = join(authDir, `workbuddy-${uid}.json`);
|
|
252
|
+
const tmp = fp + ".tmp";
|
|
253
|
+
writeFileSync(tmp, JSON.stringify(doc, null, 2), { mode: 0o600 });
|
|
254
|
+
try { if (existsSync(fp)) { const { unlinkSync, renameSync } = await import("node:fs"); unlinkSync(fp); renameSync(tmp, fp); } else { const { renameSync } = await import("node:fs"); renameSync(tmp, fp); } } catch { writeFileSync(fp, JSON.stringify(doc, null, 2), { mode: 0o600 }); }
|
|
255
|
+
} catch {}
|
|
256
|
+
} else {
|
|
257
|
+
// 未在 keys 里的 key(如临时 ring),也尝试追加
|
|
258
|
+
if (!keys.includes(newAt)) {
|
|
259
|
+
keys.push(newAt);
|
|
260
|
+
authList.push({ ...auth, refreshToken: newRt });
|
|
261
|
+
try { ring.replace(key, newAt); } catch {}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return newAt;
|
|
255
265
|
}
|
|
256
|
-
|
|
266
|
+
} catch {}
|
|
267
|
+
return null;
|
|
268
|
+
})();
|
|
269
|
+
inflightRefresh.set(dedupKey, p);
|
|
270
|
+
try { const r = await p; return r; } finally { inflightRefresh.delete(dedupKey); }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 主动续期:JWT 5 分钟内过期则后台刷新一次(不阻塞当前请求)
|
|
274
|
+
function maybeProactiveRefresh(auth, key) {
|
|
275
|
+
try {
|
|
276
|
+
const exp = decodeJwtExp(key);
|
|
277
|
+
if (!exp) return;
|
|
278
|
+
const remain = exp * 1000 - Date.now();
|
|
279
|
+
if (remain < 5 * 60 * 1000 && remain > -60 * 60 * 1000) {
|
|
280
|
+
// 剩余 <5min 且未过期太久才刷,避免每次都刷
|
|
281
|
+
void refreshTokenFor(key, auth).catch(() => {});
|
|
257
282
|
}
|
|
258
283
|
} catch {}
|
|
259
|
-
return null;
|
|
260
284
|
}
|
|
261
285
|
|
|
262
286
|
async function attemptOnce(url, body, key) {
|
|
@@ -294,6 +318,7 @@ export function createWorkbuddyProvider({
|
|
|
294
318
|
}
|
|
295
319
|
const key = keys[idx];
|
|
296
320
|
const auth = authList[idx];
|
|
321
|
+
maybeProactiveRefresh(auth, key);
|
|
297
322
|
const cached = getCachedBalance(auth.uid);
|
|
298
323
|
if (cached && Number(cached.total) === 0) {
|
|
299
324
|
const errBody = JSON.stringify({ error: `workbuddy uid in cooldown (balance 0): ${auth.uid}` });
|
|
@@ -301,9 +326,6 @@ export function createWorkbuddyProvider({
|
|
|
301
326
|
res._t = { attempts: [], waitMs: 0, totalMs: Math.round(performance.now() - t0) };
|
|
302
327
|
return res;
|
|
303
328
|
}
|
|
304
|
-
// check ring cooldown by peeking if key is cooling (ring doesn't expose, so try next and see)
|
|
305
|
-
// we enforce by attempting; if ring would skip, we still allow manual but mark cooling
|
|
306
|
-
// do single attempt with this key
|
|
307
329
|
const attempts = [];
|
|
308
330
|
let waitMs = 0;
|
|
309
331
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -318,24 +340,33 @@ export function createWorkbuddyProvider({
|
|
|
318
340
|
result._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0) };
|
|
319
341
|
throw result;
|
|
320
342
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
// 401/403 或 body 含 token 失效 → 自动续期(覆盖 workbuddy 200+JSON 错误体)
|
|
344
|
+
if (attempt === 0) {
|
|
345
|
+
let bodyText = "";
|
|
346
|
+
try { bodyText = await result.clone().text(); } catch {}
|
|
347
|
+
if (isAuthError(result.status, bodyText)) {
|
|
348
|
+
const newKey = await refreshTokenFor(key, auth);
|
|
349
|
+
if (newKey) {
|
|
350
|
+
const auth2 = authForKey(newKey);
|
|
351
|
+
const headers2 = buildAuthHeaders(newKey, auth2);
|
|
352
|
+
const finalBody = { ...body, stream: true };
|
|
353
|
+
const controller2 = new AbortController();
|
|
354
|
+
const timer2 = setTimeout(() => controller2.abort(new Error(`${id} timed out after ${connectTimeoutMs}ms`)), connectTimeoutMs);
|
|
355
|
+
try {
|
|
356
|
+
const opts2 = { method: "POST", headers: headers2, body: JSON.stringify(finalBody), signal: controller2.signal };
|
|
357
|
+
if (dispatcher) opts2.dispatcher = dispatcher;
|
|
358
|
+
let res2 = await fetchImpl(url, opts2);
|
|
359
|
+
// 刷新后若仍 401/403,视为刷新未生效,标记新 key 冷却并返回错误
|
|
360
|
+
let res2Body = "";
|
|
361
|
+
try { res2Body = await res2.clone().text(); } catch {}
|
|
362
|
+
const stillAuth = isAuthError(res2.status, res2Body);
|
|
363
|
+
res2._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0), refreshed: true };
|
|
364
|
+
res2 = withUidHeader(res2, auth2.uid || auth.uid);
|
|
365
|
+
if (stillAuth || res2.status === 429 || res2.status >= 500) activeRing.onError(newKey);
|
|
366
|
+
appendRotationLog({ uid: auth2.uid || auth.uid, model: modelForLog, totalMs: Math.round(performance.now() - t0), balanceHit: false, error: stillAuth ? `still auth ${res2.status}` : undefined });
|
|
367
|
+
return res2;
|
|
368
|
+
} catch (e2) { e2._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0) }; throw e2; } finally { clearTimeout(timer2); }
|
|
369
|
+
}
|
|
339
370
|
}
|
|
340
371
|
}
|
|
341
372
|
const entry = retry?.[result.status];
|
|
@@ -381,6 +412,8 @@ export function createWorkbuddyProvider({
|
|
|
381
412
|
err._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0) };
|
|
382
413
|
throw err;
|
|
383
414
|
}
|
|
415
|
+
// 主动续期(JWT 5min 内过期)
|
|
416
|
+
maybeProactiveRefresh(auth, key);
|
|
384
417
|
// single attempt with retry for network/429
|
|
385
418
|
let result = null;
|
|
386
419
|
let attempt = 0;
|
|
@@ -397,24 +430,45 @@ export function createWorkbuddyProvider({
|
|
|
397
430
|
result._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0) };
|
|
398
431
|
break;
|
|
399
432
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
433
|
+
// 401/403 或 body 含 token 失效 → 自动续期后重试一次;若仍失败则切下一账号
|
|
434
|
+
if (attempt === 0) {
|
|
435
|
+
let bodyText = "";
|
|
436
|
+
try { bodyText = await result.clone().text(); } catch {}
|
|
437
|
+
const needRefresh = isAuthError(result.status, bodyText);
|
|
438
|
+
if (needRefresh) {
|
|
439
|
+
const newKey = await refreshTokenFor(key, auth);
|
|
440
|
+
if (newKey) {
|
|
441
|
+
const auth2 = authForKey(newKey);
|
|
442
|
+
const headers2 = buildAuthHeaders(newKey, auth2);
|
|
443
|
+
const finalBody = { ...body, stream: true };
|
|
444
|
+
const controller2 = new AbortController();
|
|
445
|
+
const timer2 = setTimeout(() => controller2.abort(new Error(`${id} timed out after ${connectTimeoutMs}ms`)), connectTimeoutMs);
|
|
446
|
+
try {
|
|
447
|
+
const opts2 = { method: "POST", headers: headers2, body: JSON.stringify(finalBody), signal: controller2.signal };
|
|
448
|
+
if (dispatcher) opts2.dispatcher = dispatcher;
|
|
449
|
+
let res2 = await fetchImpl(url, opts2);
|
|
450
|
+
let res2Body = "";
|
|
451
|
+
try { res2Body = await res2.clone().text(); } catch {}
|
|
452
|
+
const stillAuth = isAuthError(res2.status, res2Body);
|
|
453
|
+
res2._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0), refreshed: true };
|
|
454
|
+
res2 = withUidHeader(res2, auth2.uid || uid);
|
|
455
|
+
if (stillAuth || res2.status === 429 || res2.status >= 500) activeRing.onError(newKey);
|
|
456
|
+
appendRotationLog({ uid: auth2.uid || uid, model: modelForLog, totalMs: Math.round(performance.now() - t0), balanceHit: false, error: stillAuth ? `still auth ${res2.status}` : undefined });
|
|
457
|
+
if (stillAuth) {
|
|
458
|
+
lastErr = new Error(`workbuddy auth still failing after refresh for ${uid}: ${res2Body.slice(0,120)}`);
|
|
459
|
+
lastErr._t = res2._t;
|
|
460
|
+
activeRing.onError(newKey);
|
|
461
|
+
break; // 切下一账号
|
|
462
|
+
}
|
|
463
|
+
return res2;
|
|
464
|
+
} catch (e2) { e2._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0) }; lastErr = e2; break; } finally { clearTimeout(timer2); }
|
|
465
|
+
} else {
|
|
466
|
+
// 刷新失败也切下一账号
|
|
467
|
+
lastErr = new Error(`workbuddy refresh failed for ${uid}: ${bodyText.slice(0,120)}`);
|
|
468
|
+
lastErr._t = { attempts, waitMs, totalMs: Math.round(performance.now() - t0) };
|
|
469
|
+
activeRing.onError(key);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
418
472
|
}
|
|
419
473
|
}
|
|
420
474
|
const entry = retry?.[result.status];
|
|
@@ -500,24 +554,41 @@ export function createWorkbuddyProvider({
|
|
|
500
554
|
const now = Date.now();
|
|
501
555
|
if (cache && now - fetchedAt < CACHE_TTL_MS) return cache;
|
|
502
556
|
const url = joinUrl(resolvedBase, resolvedModelsPath);
|
|
503
|
-
const
|
|
504
|
-
|
|
557
|
+
const execList = async (useKey, useAuth) => {
|
|
558
|
+
const controller2 = new AbortController();
|
|
559
|
+
const timer2 = setTimeout(() => controller2.abort(new Error(`${id} models timed out`)), 15_000);
|
|
560
|
+
try {
|
|
561
|
+
const headers = {
|
|
562
|
+
Accept: "application/json",
|
|
563
|
+
"X-User-Id": useAuth?.uid || "",
|
|
564
|
+
"X-Domain": useAuth?.domain || "www.codebuddy.cn",
|
|
565
|
+
"X-Product": "SaaS",
|
|
566
|
+
"User-Agent": "CLI/2.115.0 WorkBuddy/2.115.0",
|
|
567
|
+
Origin: "https://www.codebuddy.cn",
|
|
568
|
+
Referer: "https://www.codebuddy.cn/",
|
|
569
|
+
};
|
|
570
|
+
if (useKey) headers["Authorization"] = `Bearer ${useKey}`;
|
|
571
|
+
const opts = { headers, signal: controller2.signal };
|
|
572
|
+
if (dispatcher) opts.dispatcher = dispatcher;
|
|
573
|
+
return await fetchImpl(url, opts);
|
|
574
|
+
} finally { clearTimeout(timer2); }
|
|
575
|
+
};
|
|
505
576
|
try {
|
|
506
577
|
const key = ring.next() || keys[0] || "";
|
|
507
578
|
const auth = authForKey(key);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
579
|
+
maybeProactiveRefresh(auth, key);
|
|
580
|
+
let res = await execList(key, auth);
|
|
581
|
+
if (!res.ok) {
|
|
582
|
+
let t = "";
|
|
583
|
+
try { t = await res.clone().text(); } catch {}
|
|
584
|
+
if (isAuthError(res.status, t)) {
|
|
585
|
+
const newKey = await refreshTokenFor(key, auth);
|
|
586
|
+
if (newKey) {
|
|
587
|
+
const auth2 = authForKey(newKey);
|
|
588
|
+
res = await execList(newKey, auth2);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
521
592
|
if (!res.ok) return [];
|
|
522
593
|
const json = await res.json().catch(() => ({}));
|
|
523
594
|
const models = json?.data?.models;
|
|
@@ -528,8 +599,6 @@ export function createWorkbuddyProvider({
|
|
|
528
599
|
return cache;
|
|
529
600
|
} catch {
|
|
530
601
|
return [];
|
|
531
|
-
} finally {
|
|
532
|
-
clearTimeout(timer);
|
|
533
602
|
}
|
|
534
603
|
}
|
|
535
604
|
|