mslxdff 0.1.156 → 0.1.158
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 +4 -4
- package/docs/ARCHITECTURE.md +426 -0
- package/docs/FEATURE_TREE.md +164 -0
- package/docs/MOBILE.md +82 -0
- package/docs/adr/0001-reasoning-content-injection.md +14 -0
- package/docs/adr/0002-models-free-filter.md +12 -0
- package/docs/adr/0003-zero-state-no-auth.md +10 -0
- package/docs/adr/0004-bearer-token.md +18 -0
- package/docs/adr/0005-peer-mesh.md +53 -0
- package/docs/adr/0006-broadband-member.md +103 -0
- package/docs/adr/0007-multi-provider-prefix.md +25 -0
- package/docs/adr/0008-share-keys-to-peers.md +52 -0
- package/docs/adr/0009-chat-repl.md +37 -0
- package/docs/adr/0010-allowlist.md +36 -0
- package/docs/adr/0011-broadband-stream.md +30 -0
- package/docs/adr/0012-responses-endpoint-codex-sync.md +48 -0
- package/docs/adr/0013-node16-compat.md +41 -0
- package/docs/adr/0014-deepseek-provider.md +52 -0
- package/docs/adr/0015-upstream-probe-routing.md +50 -0
- package/docs/adr/0016-model-capabilities.md +27 -0
- package/docs/adr/0017-ai-sdk-upstream-engine.md +59 -0
- package/docs/adr/0018-zen-client-identity.md +52 -0
- package/docs/adr/0019-share-keys-always-lend.md +59 -0
- package/docs/adr/0020-zen-free-lane-agent-shape.md +52 -0
- package/docs/adr/0021-usage-report-jsonl.md +47 -0
- package/docs/adr/0022-models-capability-merge.md +61 -0
- package/docs/adr/0023-key-provider-default-direct.md +58 -0
- package/docs/adr/0024-node18-baseline.md +63 -0
- package/docs/adr/0025-workbuddy-authdir-follows-state.md +71 -0
- package/docs/adr/0026-cline-provider-id-unify.md +67 -0
- package/docs/adr/0027-codearts-provider.md +48 -0
- package/docs/adr/0028-traework-provider.md +34 -0
- package/docs/adr/0029-qoder-native-provider.md +60 -0
- package/docs/adr/0030-models-list-scoped-by-picks.md +53 -0
- package/docs/adr/0031-qoder-true-streaming.md +50 -0
- package/docs/adr/0032-generic-responses-channel.md +72 -0
- package/docs/adr/0033-cline-allowlist-auto-sync.md +75 -0
- package/docs/adr/0034-request-level-human-readable-observability.md +60 -0
- package/docs/adr/0035-sdk-channel-headers-timeout.md +49 -0
- package/docs/adr/0036-qoder-per-request-sticky-account.md +82 -0
- package/docs/adr/0037-qwenwork-independent-provider.md +82 -0
- package/docs/adr/0038-zcode-provider.md +140 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +30 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/cli_help.md +1391 -0
- package/docs/cli_help_mini.md +133 -0
- package/docs/plans/bench-via-latency-2026-09-01.md +215 -0
- package/docs/plugins.md +187 -0
- package/package.json +1 -1
- package/src/auto.js +254 -254
- package/src/bench/cline-bench.js +42 -42
- package/src/bench/probe.js +70 -70
- package/src/bench/report.js +162 -162
- package/src/bench/runner.js +77 -77
- package/src/bench/via-probe.js +124 -124
- package/src/bench/via-routes.js +87 -87
- package/src/bench/workbuddy-bench.js +54 -54
- package/src/chat/engine.js +160 -160
- package/src/chat/gateway.js +163 -163
- package/src/chat/orchestrator.js +234 -234
- package/src/chat/prompt.js +70 -70
- package/src/chat/repl.js +88 -88
- package/src/chat/terminal.js +135 -135
- package/src/chat/tools.js +306 -306
- package/src/chat-pipeline/index.js +123 -123
- package/src/chat-pipeline/policy.js +76 -76
- package/src/chat-pipeline/serial-trial.js +210 -210
- package/src/cli/commands/group.js +249 -249
- package/src/cli/commands/model/list-providers.js +1 -1
- package/src/cli/commands/model/picks.js +50 -50
- package/src/cli/commands/provider/bench-via.js +247 -247
- package/src/cli/commands/provider/bench.js +141 -141
- package/src/cli/commands/provider/index.js +124 -116
- package/src/cli/commands/provider/models.js +139 -128
- package/src/cli/commands/provider/qwenwork-login.js +119 -0
- package/src/cli/commands/provider/zcode-login.js +77 -0
- package/src/cli/commands/provider/zcode-quota.js +55 -0
- package/src/cli/commands/sync.js +232 -232
- package/src/cli/provider-row.js +2 -2
- package/src/cli/status.js +279 -279
- package/src/daemon.js +96 -96
- package/src/model-capabilities/enrich.js +86 -86
- package/src/model-capabilities/index.js +183 -183
- package/src/model-capabilities/parse.js +70 -70
- package/src/models.js +225 -225
- package/src/providers/classify.js +1 -1
- package/src/providers/cline/auth.js +228 -228
- package/src/providers/cline/chat.js +307 -307
- package/src/providers/cline.js +2 -2
- package/src/providers/keyring.js +60 -56
- package/src/providers/qoder/chat.js +183 -174
- package/src/providers/qoder/index.js +230 -185
- package/src/providers/qoder/sse.js +103 -62
- package/src/providers/qwenwork/account-store.js +133 -0
- package/src/providers/qwenwork/constants.js +67 -0
- package/src/providers/qwenwork/cosy.js +120 -0
- package/src/providers/qwenwork/crypto.js +218 -0
- package/src/providers/qwenwork/http.js +20 -0
- package/src/providers/qwenwork/index.js +327 -0
- package/src/providers/qwenwork/payload.js +142 -0
- package/src/providers/qwenwork/rsa.js +54 -0
- package/src/providers/qwenwork/sse.js +268 -0
- package/src/providers/qwenwork/stream.js +130 -0
- package/src/providers/qwenwork/upstream.js +120 -0
- package/src/providers/qwenwork.js +1 -0
- package/src/providers/registry.js +66 -56
- package/src/providers/share-keys.js +2 -2
- package/src/providers/workbuddy/chat.js +248 -248
- package/src/providers/workbuddy/reshape.js +152 -152
- package/src/providers/workbuddy.js +2 -2
- package/src/providers/zcode/account-store.js +129 -0
- package/src/providers/zcode/auth.js +28 -0
- package/src/providers/zcode/chat.js +171 -0
- package/src/providers/zcode/const.js +54 -0
- package/src/providers/zcode/headers.js +55 -0
- package/src/providers/zcode/index.js +124 -0
- package/src/providers/zcode/models.js +65 -0
- package/src/providers/zcode/oauth.js +120 -0
- package/src/providers/zcode/quota.js +176 -0
- package/src/providers/zcode/sse.js +179 -0
- package/src/reasoning.js +32 -32
- package/src/routes/chat/gateway.js +46 -46
- package/src/routes/chat/relay-pipeline.js +250 -250
- package/src/routes/chat/via-route-handler.js +144 -144
- package/src/routes/hedge.js +255 -255
- package/src/routes/models-route.js +167 -167
- package/src/routes/peers.js +273 -273
- package/src/routes/stream.js +438 -438
- package/src/runtime/bootstrap.js +45 -45
- package/src/runtime/provider-gate.js +33 -30
- package/src/runtime/providers-setup.js +165 -165
- package/src/server.js +64 -64
- package/src/state/schemas/allowlist.js +92 -92
- package/src/sync-opencode.js +280 -280
- package/src/transport/index.js +244 -244
- package/src/transport/pool.js +56 -56
- package/src/transport/retry.js +24 -24
- package/src/transport/sse.js +93 -93
- package/src/upstream-probe/display.js +52 -52
- package/src/upstream-probe/probe.js +49 -49
- package/src/upstream-probe/rotate.js +110 -110
- package/src/upstream-probe/start.js +45 -45
- package/src/upstream.js +289 -289
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Anthropic SSE → OpenAI 形状转换(流式 / 聚合两种出口)。
|
|
2
|
+
// 承诺字段:文本增量、tool_use 增量(tool_calls)、usage、finish_reason;thinking 块丢弃(不入历史)。
|
|
3
|
+
const ENCODER = new TextEncoder();
|
|
4
|
+
const DECODER = new TextDecoder();
|
|
5
|
+
const STOP_REASONS = { end_turn: "stop", max_tokens: "length", tool_use: "tool_calls", stop_sequence: "stop", pause_turn: "stop" };
|
|
6
|
+
const newId = () => `chatcmpl-zcode-${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
|
|
7
|
+
|
|
8
|
+
function dataPayload(block) {
|
|
9
|
+
const lines = String(block || "").split("\n").filter((l) => l.startsWith("data:"));
|
|
10
|
+
if (!lines.length) return null;
|
|
11
|
+
const joined = lines.map((l) => l.slice(5).trimStart()).join("\n").trim();
|
|
12
|
+
if (!joined || joined === "[DONE]") return null;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(joined);
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createAnthropicTranslator({ model = "zcode", id = newId(), created = Math.floor(Date.now() / 1000) } = {}) {
|
|
21
|
+
let buffer = "";
|
|
22
|
+
let roleSent = false;
|
|
23
|
+
let tailSent = false;
|
|
24
|
+
let stopReason = null;
|
|
25
|
+
let error = null;
|
|
26
|
+
let inputTokens = null;
|
|
27
|
+
let outputTokens = null;
|
|
28
|
+
let text = "";
|
|
29
|
+
let toolCount = 0;
|
|
30
|
+
const toolIndexByBlock = new Map();
|
|
31
|
+
const toolCalls = [];
|
|
32
|
+
|
|
33
|
+
const usage = () => {
|
|
34
|
+
if (inputTokens == null && outputTokens == null) return undefined;
|
|
35
|
+
const prompt = inputTokens ?? 0;
|
|
36
|
+
const completion = outputTokens ?? 0;
|
|
37
|
+
return { prompt_tokens: prompt, completion_tokens: completion, total_tokens: prompt + completion };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const chunkLine = (delta, finish = null, u) => {
|
|
41
|
+
const chunk = { id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta, finish_reason: finish }] };
|
|
42
|
+
if (u) chunk.usage = u;
|
|
43
|
+
return `data: ${JSON.stringify(chunk)}\n\n`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function handle(evt) {
|
|
47
|
+
const out = [];
|
|
48
|
+
if (!evt || typeof evt !== "object") return out;
|
|
49
|
+
if (evt.type === "message_start") {
|
|
50
|
+
const it = evt.message?.usage?.input_tokens ?? evt.usage?.input_tokens;
|
|
51
|
+
if (it != null) inputTokens = Number(it) || 0;
|
|
52
|
+
if (!roleSent) {
|
|
53
|
+
roleSent = true;
|
|
54
|
+
out.push(chunkLine({ role: "assistant", content: "" }));
|
|
55
|
+
}
|
|
56
|
+
} else if (evt.type === "content_block_start") {
|
|
57
|
+
const block = evt.content_block || {};
|
|
58
|
+
if (block.type === "tool_use") {
|
|
59
|
+
const idx = toolCount++;
|
|
60
|
+
toolIndexByBlock.set(Number(evt.index), idx);
|
|
61
|
+
toolCalls.push({ id: String(block.id || `toolu_${idx}`), name: String(block.name || ""), args: "" });
|
|
62
|
+
out.push(chunkLine({ tool_calls: [{ index: idx, id: String(block.id || ""), type: "function", function: { name: String(block.name || ""), arguments: "" } }] }));
|
|
63
|
+
}
|
|
64
|
+
} else if (evt.type === "content_block_delta") {
|
|
65
|
+
const d = evt.delta || {};
|
|
66
|
+
if (d.type === "text_delta" && typeof d.text === "string") {
|
|
67
|
+
text += d.text;
|
|
68
|
+
out.push(chunkLine({ content: d.text }));
|
|
69
|
+
} else if (d.type === "input_json_delta" && typeof d.partial_json === "string") {
|
|
70
|
+
const idx = toolIndexByBlock.has(Number(evt.index)) ? toolIndexByBlock.get(Number(evt.index)) : Math.max(0, toolCount - 1);
|
|
71
|
+
if (toolCalls[idx]) toolCalls[idx].args += d.partial_json;
|
|
72
|
+
out.push(chunkLine({ tool_calls: [{ index: idx, function: { arguments: d.partial_json } }] }));
|
|
73
|
+
}
|
|
74
|
+
} else if (evt.type === "message_delta") {
|
|
75
|
+
const r = evt.delta?.stop_reason;
|
|
76
|
+
if (r) stopReason = STOP_REASONS[r] || "stop";
|
|
77
|
+
if (evt.usage?.output_tokens != null) outputTokens = Number(evt.usage.output_tokens) || 0;
|
|
78
|
+
} else if (evt.type === "message_stop") {
|
|
79
|
+
out.push(chunkLine({}, stopReason || "stop", usage()));
|
|
80
|
+
out.push("data: [DONE]\n\n");
|
|
81
|
+
tailSent = true;
|
|
82
|
+
} else if (evt.type === "error") {
|
|
83
|
+
error = { kind: "server", code: 0, message: String(evt.error?.message || "上游流内错误") };
|
|
84
|
+
out.push(`data: ${JSON.stringify({ error: { message: error.message, type: String(evt.error?.type || "upstream_error") } })}\n\n`);
|
|
85
|
+
out.push("data: [DONE]\n\n");
|
|
86
|
+
tailSent = true;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function feed(textChunk) {
|
|
92
|
+
buffer += String(textChunk ?? "").replace(/\r\n/g, "\n");
|
|
93
|
+
const out = [];
|
|
94
|
+
let idx;
|
|
95
|
+
while ((idx = buffer.indexOf("\n\n")) >= 0) {
|
|
96
|
+
const block = buffer.slice(0, idx);
|
|
97
|
+
buffer = buffer.slice(idx + 2);
|
|
98
|
+
const payload = dataPayload(block);
|
|
99
|
+
if (payload) out.push(...handle(payload));
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function flush() {
|
|
105
|
+
if (tailSent) return [];
|
|
106
|
+
tailSent = true;
|
|
107
|
+
const out = [];
|
|
108
|
+
if (!roleSent) out.push(chunkLine({ role: "assistant", content: "" }));
|
|
109
|
+
out.push(chunkLine({}, stopReason || "stop", usage()));
|
|
110
|
+
out.push("data: [DONE]\n\n");
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function result() {
|
|
115
|
+
const message = { role: "assistant", content: text || null };
|
|
116
|
+
if (toolCalls.length) {
|
|
117
|
+
message.tool_calls = toolCalls.map((t) => ({ id: t.id, type: "function", function: { name: t.name, arguments: t.args || "{}" } }));
|
|
118
|
+
if (!text) message.content = null;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
error,
|
|
122
|
+
openAi: {
|
|
123
|
+
id,
|
|
124
|
+
object: "chat.completion",
|
|
125
|
+
created,
|
|
126
|
+
model,
|
|
127
|
+
choices: [{ index: 0, message, finish_reason: stopReason || "stop" }],
|
|
128
|
+
usage: usage() || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { feed, flush, result };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function anthropicToOpenAiStream(upstream, options = {}) {
|
|
137
|
+
const translator = createAnthropicTranslator(options);
|
|
138
|
+
if (!upstream || typeof upstream.getReader !== "function") {
|
|
139
|
+
return new ReadableStream({
|
|
140
|
+
start(controller) {
|
|
141
|
+
for (const s of [...translator.feed(String(upstream ?? "")), ...translator.flush()]) controller.enqueue(ENCODER.encode(s));
|
|
142
|
+
controller.close();
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const reader = upstream.getReader();
|
|
147
|
+
return new ReadableStream({
|
|
148
|
+
async pull(controller) {
|
|
149
|
+
// 注意:pull 必须「有产出或收流」才返回——若本次不 enqueue,流规范不会再触发下一次 pull,
|
|
150
|
+
// 消费端 read() 会永久挂起(首个分片落在事件中间时必现)。故此处循环读到有产出为止。
|
|
151
|
+
for (;;) {
|
|
152
|
+
let next;
|
|
153
|
+
try {
|
|
154
|
+
next = await reader.read();
|
|
155
|
+
} catch {
|
|
156
|
+
next = { done: true };
|
|
157
|
+
}
|
|
158
|
+
if (next?.done) {
|
|
159
|
+
for (const s of translator.flush()) controller.enqueue(ENCODER.encode(s));
|
|
160
|
+
controller.close();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const t = DECODER.decode(next.value, { stream: true });
|
|
164
|
+
const out = translator.feed(t);
|
|
165
|
+
if (out.length) {
|
|
166
|
+
for (const s of out) controller.enqueue(ENCODER.encode(s));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function aggregateAnthropicToOpenAi(sseText, options = {}) {
|
|
175
|
+
const translator = createAnthropicTranslator(options);
|
|
176
|
+
translator.feed(String(sseText ?? ""));
|
|
177
|
+
translator.flush();
|
|
178
|
+
return translator.result();
|
|
179
|
+
}
|
package/src/reasoning.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
const PLACEHOLDER = " ";
|
|
2
|
-
|
|
3
|
-
const MODEL_RULES = [
|
|
4
|
-
{ match: (m) => /^kimi-/i.test(m || ""), scope: "toolCalls" },
|
|
5
|
-
{ match: (m) => /deepseek/i.test(m || ""), scope: "all" },
|
|
6
|
-
];
|
|
7
|
-
|
|
8
|
-
export function normalizeModel(model) {
|
|
9
|
-
return model.startsWith("oc/") ? model.slice(3) : model;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function shouldInject(message, scope) {
|
|
13
|
-
if (message?.role !== "assistant") return false;
|
|
14
|
-
const rc = message.reasoning_content;
|
|
15
|
-
if (typeof rc === "string" && rc.length > 0) return false;
|
|
16
|
-
if (scope === "toolCalls") {
|
|
17
|
-
return Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
18
|
-
}
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function applyRule(body, rule) {
|
|
23
|
-
if (!rule || !body?.messages) return body;
|
|
24
|
-
const messages = body.messages.map((m) =>
|
|
25
|
-
shouldInject(m, rule.scope) ? { ...m, reasoning_content: PLACEHOLDER } : m
|
|
26
|
-
);
|
|
27
|
-
return { ...body, messages };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function injectReasoningContent(model, body) {
|
|
31
|
-
const rule = MODEL_RULES.find((r) => r.match(model));
|
|
32
|
-
return applyRule(body, rule);
|
|
1
|
+
const PLACEHOLDER = " ";
|
|
2
|
+
|
|
3
|
+
const MODEL_RULES = [
|
|
4
|
+
{ match: (m) => /^kimi-/i.test(m || ""), scope: "toolCalls" },
|
|
5
|
+
{ match: (m) => /deepseek/i.test(m || ""), scope: "all" },
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export function normalizeModel(model) {
|
|
9
|
+
return model.startsWith("oc/") ? model.slice(3) : model;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function shouldInject(message, scope) {
|
|
13
|
+
if (message?.role !== "assistant") return false;
|
|
14
|
+
const rc = message.reasoning_content;
|
|
15
|
+
if (typeof rc === "string" && rc.length > 0) return false;
|
|
16
|
+
if (scope === "toolCalls") {
|
|
17
|
+
return Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyRule(body, rule) {
|
|
23
|
+
if (!rule || !body?.messages) return body;
|
|
24
|
+
const messages = body.messages.map((m) =>
|
|
25
|
+
shouldInject(m, rule.scope) ? { ...m, reasoning_content: PLACEHOLDER } : m
|
|
26
|
+
);
|
|
27
|
+
return { ...body, messages };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function injectReasoningContent(model, body) {
|
|
31
|
+
const rule = MODEL_RULES.find((r) => r.match(model));
|
|
32
|
+
return applyRule(body, rule);
|
|
33
33
|
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { clientIp, json, readBody, parseHops } from "../helpers.js";
|
|
2
|
-
import { runHook } from "../../plugins.js";
|
|
3
|
-
import { createChatPipeline } from "../../chat-pipeline/index.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* ChatGateway 薄适配层:仅负责 HTTP I/O(readBody + request:received hook)→ 委托 ChatPipeline.execute
|
|
7
|
-
* Policy/Order/AutoRace/hedge/peer/broadband 全部在 chat-pipeline 深模块内。
|
|
8
|
-
*/
|
|
9
|
-
export function createChatGateway(deps = {}) {
|
|
10
|
-
const pipeline = createChatPipeline(deps);
|
|
11
|
-
|
|
12
|
-
async function handle({ req, res }) {
|
|
13
|
-
let body;
|
|
14
|
-
try {
|
|
15
|
-
body = await readBody(req);
|
|
16
|
-
} catch {
|
|
17
|
-
return json(res, 400, { error: "Invalid JSON body" });
|
|
18
|
-
}
|
|
19
|
-
if (deps.plugins?.length) {
|
|
20
|
-
const rc = await runHook(deps.plugins, "request:received", {
|
|
21
|
-
ip: clientIp(req),
|
|
22
|
-
hops: parseHops(req.headers["x-mslxdff-hops"]),
|
|
23
|
-
headers: { "content-type": req.headers["content-type"] },
|
|
24
|
-
body,
|
|
25
|
-
});
|
|
26
|
-
for (const e of rc.errors) deps.logs?.appendEvent?.({ ts: Date.now(), type: "plugin-hook-error", hook: "request:received", plugin: e.plugin, error: e.error });
|
|
27
|
-
const respond = rc.value?.respond;
|
|
28
|
-
if (respond && typeof respond === "object") return json(res, respond.status || 200, respond.body ?? {});
|
|
29
|
-
}
|
|
30
|
-
req.body = body;
|
|
31
|
-
try {
|
|
32
|
-
await pipeline.execute({ req, res });
|
|
33
|
-
} catch (err) {
|
|
34
|
-
const msg = err?.message || String(err);
|
|
35
|
-
if (!res.headersSent) return json(res, 502, { error: msg });
|
|
36
|
-
try { res.end(); } catch {}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { handle };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 薄适配:保持原 chatHandler 签名兼容
|
|
44
|
-
export async function chatHandler(ctx) {
|
|
45
|
-
const gw = createChatGateway(ctx);
|
|
46
|
-
return gw.handle(ctx);
|
|
1
|
+
import { clientIp, json, readBody, parseHops } from "../helpers.js";
|
|
2
|
+
import { runHook } from "../../plugins.js";
|
|
3
|
+
import { createChatPipeline } from "../../chat-pipeline/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ChatGateway 薄适配层:仅负责 HTTP I/O(readBody + request:received hook)→ 委托 ChatPipeline.execute
|
|
7
|
+
* Policy/Order/AutoRace/hedge/peer/broadband 全部在 chat-pipeline 深模块内。
|
|
8
|
+
*/
|
|
9
|
+
export function createChatGateway(deps = {}) {
|
|
10
|
+
const pipeline = createChatPipeline(deps);
|
|
11
|
+
|
|
12
|
+
async function handle({ req, res }) {
|
|
13
|
+
let body;
|
|
14
|
+
try {
|
|
15
|
+
body = await readBody(req);
|
|
16
|
+
} catch {
|
|
17
|
+
return json(res, 400, { error: "Invalid JSON body" });
|
|
18
|
+
}
|
|
19
|
+
if (deps.plugins?.length) {
|
|
20
|
+
const rc = await runHook(deps.plugins, "request:received", {
|
|
21
|
+
ip: clientIp(req),
|
|
22
|
+
hops: parseHops(req.headers["x-mslxdff-hops"]),
|
|
23
|
+
headers: { "content-type": req.headers["content-type"] },
|
|
24
|
+
body,
|
|
25
|
+
});
|
|
26
|
+
for (const e of rc.errors) deps.logs?.appendEvent?.({ ts: Date.now(), type: "plugin-hook-error", hook: "request:received", plugin: e.plugin, error: e.error });
|
|
27
|
+
const respond = rc.value?.respond;
|
|
28
|
+
if (respond && typeof respond === "object") return json(res, respond.status || 200, respond.body ?? {});
|
|
29
|
+
}
|
|
30
|
+
req.body = body;
|
|
31
|
+
try {
|
|
32
|
+
await pipeline.execute({ req, res });
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const msg = err?.message || String(err);
|
|
35
|
+
if (!res.headersSent) return json(res, 502, { error: msg });
|
|
36
|
+
try { res.end(); } catch {}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { handle };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 薄适配:保持原 chatHandler 签名兼容
|
|
44
|
+
export async function chatHandler(ctx) {
|
|
45
|
+
const gw = createChatGateway(ctx);
|
|
46
|
+
return gw.handle(ctx);
|
|
47
47
|
}
|