mslxdff 0.1.215 → 0.1.216
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
|
@@ -10,7 +10,11 @@ export function createGate({ limit = 3 } = {}) {
|
|
|
10
10
|
if (w) w();
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// 取得一个槽位,返回「释放一次」的幂等函数。超限时 FIFO 排队(并回调 onWait)。
|
|
14
|
+
// 关键:释放的时机交给调用方——流式请求必须**等流真正读完/取消**才释放,
|
|
15
|
+
// 否则 runChat 一返回(流还没消费)就放行,上游其实仍占着会话 ⇒ 第 4 路被上游拒
|
|
16
|
+
// (TM.00001041 并发会话数已达上限)。见 index.js 的 chat 接线。
|
|
17
|
+
async function acquire(onWait) {
|
|
14
18
|
if (active >= cap) {
|
|
15
19
|
const t0 = Date.now();
|
|
16
20
|
const queued = waiters.length + 1;
|
|
@@ -18,15 +22,26 @@ export function createGate({ limit = 3 } = {}) {
|
|
|
18
22
|
if (onWait) { try { onWait({ waitedMs: Date.now() - t0, queued, limit: cap }); } catch {} }
|
|
19
23
|
}
|
|
20
24
|
active++;
|
|
25
|
+
let released = false;
|
|
26
|
+
return function releaseOnce() {
|
|
27
|
+
if (released) return;
|
|
28
|
+
released = true;
|
|
29
|
+
release();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function run(fn, onWait) {
|
|
34
|
+
const rel = await acquire(onWait);
|
|
21
35
|
try {
|
|
22
36
|
return await fn();
|
|
23
37
|
} finally {
|
|
24
|
-
|
|
38
|
+
rel();
|
|
25
39
|
}
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
return {
|
|
29
43
|
run,
|
|
44
|
+
acquire,
|
|
30
45
|
stats: () => ({ active, queued: waiters.length, limit: cap }),
|
|
31
46
|
get active() { return active; },
|
|
32
47
|
get queued() { return waiters.length; },
|
|
@@ -82,9 +82,48 @@ export function createCodeartsProvider({
|
|
|
82
82
|
const onWait = (info) => {
|
|
83
83
|
try { appendEvent({ type: "codearts-queue", ...info }); } catch {}
|
|
84
84
|
};
|
|
85
|
+
// 流式请求必须在**流真正读完/取消后**才释放并发槽——否则 runChat 一返回(body 还没消费)
|
|
86
|
+
// 就 release,上游其实仍占着会话 ⇒ 第 4 路被上游拒(TM.00001041 并发会话数已达上限)。
|
|
87
|
+
// 非流式(JSON)在 runChat 内部已聚合完,可立即释放。返回的 Response 保持原 body 引用,
|
|
88
|
+
// 只包一层 tee 触发 release;出错也保证释放一次(releaseOnce 幂等)。
|
|
89
|
+
function holdUntilBodyDone(res, releaseOnce) {
|
|
90
|
+
const ct = res?.headers?.get?.("content-type") || "";
|
|
91
|
+
if (!ct.includes("text/event-stream") || !res.body) {
|
|
92
|
+
releaseOnce();
|
|
93
|
+
return res;
|
|
94
|
+
}
|
|
95
|
+
let done = false;
|
|
96
|
+
const fin = () => { if (!done) { done = true; releaseOnce(); } };
|
|
97
|
+
const onErr = () => fin();
|
|
98
|
+
try {
|
|
99
|
+
const reader = res.body.getReader();
|
|
100
|
+
const stream = new ReadableStream({
|
|
101
|
+
async pull(controller) {
|
|
102
|
+
try {
|
|
103
|
+
const { done: d, value } = await reader.read();
|
|
104
|
+
if (d) { fin(); controller.close(); return; }
|
|
105
|
+
controller.enqueue(value);
|
|
106
|
+
} catch (e) { onErr(); controller.error(e); }
|
|
107
|
+
},
|
|
108
|
+
cancel(reason) { fin(); try { return reader.cancel(reason); } catch { /* ignore */ } },
|
|
109
|
+
});
|
|
110
|
+
return new Response(stream, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
111
|
+
} catch {
|
|
112
|
+
fin();
|
|
113
|
+
return res;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
85
116
|
async function chat(body, opts = {}) {
|
|
86
117
|
if (!ring.size) throw new Error("codearts: missing credential — run: mslxdff -provider codearts login");
|
|
87
|
-
|
|
118
|
+
const rel = await gate.acquire(onWait);
|
|
119
|
+
let res;
|
|
120
|
+
try {
|
|
121
|
+
res = await chatSvc.runChat(body, ring, opts);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
rel();
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
return holdUntilBodyDone(res, rel);
|
|
88
127
|
}
|
|
89
128
|
async function chatWithKeys(body, keys, opts = {}) {
|
|
90
129
|
const blobs = (keys || []).filter((k) => typeof k === "string" && k.trim().startsWith("{"));
|
|
@@ -92,7 +131,15 @@ export function createCodeartsProvider({
|
|
|
92
131
|
const tmpRing = createKeyRing(blobs, { cooldownMs });
|
|
93
132
|
const tmpPool = createAuthPool({ keys: blobs, fetchImpl, stsHost, clock }); // 借用凭据不写回本节点
|
|
94
133
|
const tmpSvc = createChatService({ id, baseUrl: chatSvcBase, chatPath, fetchImpl, dispatcher: null, authPool: tmpPool, catalog, connectTimeoutMs, clock });
|
|
95
|
-
|
|
134
|
+
const rel = await gate.acquire(onWait);
|
|
135
|
+
let res;
|
|
136
|
+
try {
|
|
137
|
+
res = await tmpSvc.runChat(body, tmpRing, opts);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
rel();
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
return holdUntilBodyDone(res, rel);
|
|
96
143
|
}
|
|
97
144
|
|
|
98
145
|
// 三路发现(主账号);结果写目录(benefit 判定按账号隔离)
|