mslxdff 0.1.99 → 0.1.100
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
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// DeepSeek 账号池 + 登录(Android 协议)
|
|
2
2
|
// 上游协议参考 iidamie/deepseek2api(GPL-3.0,协议事实)
|
|
3
|
+
// 并发闸门参考 Chat2API-WXS per-account guard:每 token 在途限 1,全忙 FIFO 排队,流结束释放
|
|
3
4
|
import { compatFetch } from "../../compat.js";
|
|
5
|
+
import { envInt } from "../base.js";
|
|
4
6
|
import { androidHeaders } from "./pow.js";
|
|
5
7
|
import { DEEPSEEK_DEFAULT_BASE, DEEPSEEK_API_PREFIX } from "./pow.js";
|
|
6
8
|
import { dsDebug } from "./debug.js";
|
|
@@ -56,7 +58,7 @@ export async function loginDeepseek({ loginValue, password, areaCode = "+86", fe
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
// 多账号轮换池:空闲最久优先 + 冷却 + onError;轮换语义对齐 NIyueeE/ds-free-api(选空闲最久者,最大化每次使用间隔)
|
|
59
|
-
export function createAuthPool({ tokens = [], cooldownMs = DEFAULT_COOLDOWN_MS, clock = Date.now } = {}) {
|
|
61
|
+
export function createAuthPool({ tokens = [], cooldownMs = DEFAULT_COOLDOWN_MS, clock = Date.now, maxConcurrentPerToken: maxConcParam } = {}) {
|
|
60
62
|
const list = [...new Set((tokens || []).map((t) => String(t).trim()).filter(Boolean))];
|
|
61
63
|
const until = new Map(); // token → 冷却到期时刻
|
|
62
64
|
const lastUsedAt = new Map(); // token → 最近一次被取用时刻(空闲最久排序依据)
|
|
@@ -107,5 +109,86 @@ export function createAuthPool({ tokens = [], cooldownMs = DEFAULT_COOLDOWN_MS,
|
|
|
107
109
|
return token;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// 并发闸门:每 token 最多 maxConcurrentPerToken 个在途请求(默认 1 = 禁止并发)
|
|
114
|
+
// acquireSlot → { token, release } | null(超时);release 幂等,流式在 cleanup 链释放
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
const maxConcurrentPerToken = Math.max(1, Number(maxConcParam) > 0 ? Number(maxConcParam) : envInt("MSLXDFF_DEEPSEEK_MAX_CONCURRENT", 1));
|
|
117
|
+
const inFlight = new Map(); // token → 在途数
|
|
118
|
+
const waiters = []; // FIFO 等待队列:{ resolve, timer }
|
|
119
|
+
|
|
120
|
+
function inflightOf(token) {
|
|
121
|
+
return inFlight.get(token) || 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 空闲(未冷却且在途未满)的号里选空闲最久者;无 → null
|
|
125
|
+
function nextIdle() {
|
|
126
|
+
let best = null;
|
|
127
|
+
let bestIdle = -1;
|
|
128
|
+
for (const token of list) {
|
|
129
|
+
if (isCooling(token) || inflightOf(token) >= maxConcurrentPerToken) continue;
|
|
130
|
+
const idle = clock() - (lastUsedAt.get(token) || 0);
|
|
131
|
+
if (idle > bestIdle) {
|
|
132
|
+
bestIdle = idle;
|
|
133
|
+
best = token;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return best;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function makeRelease(token) {
|
|
140
|
+
let released = false;
|
|
141
|
+
return () => {
|
|
142
|
+
if (released) return;
|
|
143
|
+
released = true;
|
|
144
|
+
inFlight.set(token, Math.max(0, inflightOf(token) - 1));
|
|
145
|
+
dsDebug("gate", { event: "release", tokenTail: String(token).slice(-6), inFlight: inflightOf(token) });
|
|
146
|
+
wakeNext();
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 有槽位释放 → 唤醒队首等待者重新竞争(重新走 nextIdle,天然跳过冷却/满载号)
|
|
151
|
+
function wakeNext() {
|
|
152
|
+
while (waiters.length) {
|
|
153
|
+
const w = waiters.shift();
|
|
154
|
+
clearTimeout(w.timer);
|
|
155
|
+
const token = nextIdle();
|
|
156
|
+
if (!token) { waiters.unshift(w); break; } // 没有可用槽位,塞回队首等下次 release
|
|
157
|
+
lastUsedAt.set(token, clock());
|
|
158
|
+
inFlight.set(token, inflightOf(token) + 1);
|
|
159
|
+
dsDebug("gate", { event: "wakeup", tokenTail: String(token).slice(-6), queue: waiters.length });
|
|
160
|
+
w.resolve({ token, release: makeRelease(token) });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function acquireSlot({ timeoutMs } = {}) {
|
|
165
|
+
const timeout = Number(timeoutMs) > 0 ? Number(timeoutMs) : envInt("MSLXDFF_DEEPSEEK_QUEUE_TIMEOUT_MS", 30_000);
|
|
166
|
+
const token = nextIdle();
|
|
167
|
+
if (token) {
|
|
168
|
+
lastUsedAt.set(token, clock());
|
|
169
|
+
inFlight.set(token, inflightOf(token) + 1);
|
|
170
|
+
dsDebug("gate", { event: "acquire-direct", tokenTail: String(token).slice(-6), inFlight: inflightOf(token) });
|
|
171
|
+
return { token, release: makeRelease(token) };
|
|
172
|
+
}
|
|
173
|
+
if (!list.length) {
|
|
174
|
+
const err = new Error("缺少 DeepSeek 凭据,请先 -provider deepseek login 或设置 providerConfigs.deepseek.keys");
|
|
175
|
+
err._deepseekNoAuth = true;
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
// 全忙:入 FIFO 队列等待 release 唤醒;超时返回 null。
|
|
179
|
+
// 定时器不 unref:有请求在排队 = 有未完成工作,事件循环必须等它(unref 会让进程提前退出)
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
const timer = setTimeout(() => {
|
|
182
|
+
const i = waiters.indexOf(w);
|
|
183
|
+
if (i >= 0) waiters.splice(i, 1);
|
|
184
|
+
dsDebug("gate", { event: "acquire-timeout", queue: waiters.length });
|
|
185
|
+
resolve(null);
|
|
186
|
+
}, timeout);
|
|
187
|
+
const w = { resolve, timer };
|
|
188
|
+
waiters.push(w);
|
|
189
|
+
dsDebug("gate", { event: "enqueue", queue: waiters.length, timeoutMs: timeout });
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { next, onError, available, requireToken, acquireSlot, maxConcurrentPerToken, size: list.length, cooldownMs, keys: [...list] };
|
|
111
194
|
}
|
|
@@ -183,17 +183,26 @@ export async function runDeepseekChat({ body, authPool, fetchImpl, dispatcher, b
|
|
|
183
183
|
|
|
184
184
|
const maxAttempts = maxAuthRetries ?? Math.max(1, authPool.size);
|
|
185
185
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
186
|
-
|
|
186
|
+
// 并发闸门:每 token 在途限 1(MSLXDFF_DEEPSEEK_MAX_CONCURRENT),全忙排队,超时人话 429
|
|
187
|
+
const slot = await authPool.acquireSlot({});
|
|
188
|
+
if (!slot) {
|
|
189
|
+
throw upstreamError("DeepSeek: 所有账号都在忙(并发闸门排队超时),稍后重试或调大 MSLXDFF_DEEPSEEK_QUEUE_TIMEOUT_MS / MSLXDFF_DEEPSEEK_MAX_CONCURRENT", { status: 429 });
|
|
190
|
+
}
|
|
191
|
+
const { token, release } = slot;
|
|
187
192
|
try {
|
|
188
193
|
const out = prompt.length > threshold
|
|
189
194
|
? await runChunked({ token, flags, prompt, isStream, fetchImpl, dispatcher, baseUrl, connectTimeoutMs, threshold })
|
|
190
195
|
: await runOnce({ token, flags, prompt, isStream, fetchImpl, dispatcher, baseUrl, connectTimeoutMs });
|
|
191
196
|
if (out.kind === "stream") {
|
|
197
|
+
// 流式:槽位由 cleanup 链释放(buildOpenAiSseStream 的 done/error/cancel),此处不放
|
|
198
|
+
const prevCleanup = out.cleanup;
|
|
192
199
|
return {
|
|
193
200
|
kind: "stream",
|
|
194
201
|
res: out.res,
|
|
195
202
|
token,
|
|
196
|
-
cleanup: () =>
|
|
203
|
+
cleanup: async () => {
|
|
204
|
+
try { await prevCleanup?.(); } finally { release(); }
|
|
205
|
+
},
|
|
197
206
|
};
|
|
198
207
|
}
|
|
199
208
|
const sseText = await readBodyText(out.res);
|
|
@@ -209,11 +218,15 @@ export async function runDeepseekChat({ body, authPool, fetchImpl, dispatcher, b
|
|
|
209
218
|
dsDump("aggregate", "EMPTY aggregate content! full sseText", sseText, 8000);
|
|
210
219
|
}
|
|
211
220
|
await deleteChatSession({ token: out.token, sessionId: out.sessionId, fetchImpl, dispatcher, baseUrl });
|
|
221
|
+
release(); // 聚合成功:槽位随会话删除一并释放
|
|
212
222
|
return { kind: "json", data };
|
|
213
223
|
} catch (err) {
|
|
224
|
+
release(); // 失败(含 rotateAuth 重试):立即释放,避免槽位泄漏
|
|
214
225
|
dsError(`chat attempt=${attempt}`, err);
|
|
215
226
|
if (err?._rotateAuth) authPool.onError(token, { cooldownMs: err._cooldownMs });
|
|
216
|
-
|
|
227
|
+
// 会话创建/删除阶段的 401/403 也属凭据被拒(session.js 带 status),与 completion 阶段对齐
|
|
228
|
+
else if (err?.status === 401 || err?.status === 403) authPool.onError(token, { cooldownMs: COOLDOWN_PRESETS.default });
|
|
229
|
+
if (!(err?._rotateAuth || err?.status === 401 || err?.status === 403) || attempt >= maxAttempts - 1) throw err;
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
throw upstreamError("DeepSeek: 所有账号均不可用", {});
|
|
@@ -30,7 +30,10 @@ export async function createChatSession({ token, fetchImpl, dispatcher, baseUrl
|
|
|
30
30
|
const id = data?.data?.biz_data?.chat_session?.id ?? data?.data?.biz_data?.id;
|
|
31
31
|
if (!res.ok || data?.data?.biz_code !== 0 || !id) {
|
|
32
32
|
const msg = data?.data?.biz_msg || data?.msg || "响应缺少会话 id";
|
|
33
|
-
|
|
33
|
+
const err = new Error(`DeepSeek 会话创建失败: ${msg}${res.ok ? "" : ` (http ${res.status})`}`);
|
|
34
|
+
// 401/403 = 凭据被拒:带 status 供上层 rotateAuth(与 completion 阶段 classifyFailure 对齐)
|
|
35
|
+
if (res.status === 401 || res.status === 403) err.status = res.status;
|
|
36
|
+
throw err;
|
|
34
37
|
}
|
|
35
38
|
return id;
|
|
36
39
|
}
|