mslxdff 0.1.45 → 0.1.55
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/README.md +162 -162
- package/bin/mslxdff.js +202 -21
- package/docs/adr/0001-reasoning-content-injection.md +13 -13
- package/docs/adr/0002-models-free-filter.md +11 -11
- package/docs/adr/0003-zero-state-no-auth.md +9 -9
- package/docs/adr/0004-bearer-token.md +17 -17
- package/docs/agents/domain.md +50 -50
- package/docs/agents/issue-tracker.md +29 -29
- package/docs/agents/triage-labels.md +14 -14
- package/package.json +1 -1
- package/src/auto.js +20 -2
- package/src/chooser.js +12 -6
- package/src/daemon.js +84 -84
- package/src/logs.js +41 -2
- package/src/models.js +127 -127
- package/src/reasoning.js +32 -32
- package/src/routes/chat/broadband-handler.js +88 -0
- package/src/routes/chat/exhausted-handler.js +41 -0
- package/src/routes/chat/hedge-handler.js +142 -0
- package/src/routes/chat/index.js +178 -0
- package/src/routes/chat/local-handler.js +88 -0
- package/src/routes/chat/peer-handler.js +58 -0
- package/src/routes/chat.js +3 -342
- package/src/routes/hedge.js +251 -0
- package/src/routes/peers.js +49 -15
- package/src/server.js +57 -57
- package/src/state.js +285 -152
- package/src/sync-workbuddy.js +104 -0
- package/src/upstream.js +311 -212
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { injectReasoningContent, normalizeModel } from "../../reasoning.js";
|
|
3
|
+
import { isAutoModel } from "../../auto.js";
|
|
4
|
+
import { clientIp, json, readBody, parseHops, summarizePrompt, errMsg } from "../helpers.js";
|
|
5
|
+
import { hedgeDelayMs, shouldHedge } from "../hedge.js";
|
|
6
|
+
import { runHook } from "../../plugins.js";
|
|
7
|
+
import { handleHedge } from "./hedge-handler.js";
|
|
8
|
+
import { handleLocalRelay } from "./local-handler.js";
|
|
9
|
+
import { handlePeerRelay } from "./peer-handler.js";
|
|
10
|
+
import { handleBroadbandRelay } from "./broadband-handler.js";
|
|
11
|
+
import { handleExhaustedLocal, handleExhaustedAll } from "./exhausted-handler.js";
|
|
12
|
+
|
|
13
|
+
export async function chatHandler({ req, res, upstream, auto, logs, peers, maxHops, groups, bus, token, plugins }) {
|
|
14
|
+
let body;
|
|
15
|
+
try {
|
|
16
|
+
body = await readBody(req);
|
|
17
|
+
} catch {
|
|
18
|
+
return json(res, 400, { error: "Invalid JSON body" });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (plugins?.length) {
|
|
22
|
+
const rc = await runHook(plugins, "request:received", { ip: clientIp(req), hops: parseHops(req.headers["x-mslxdff-hops"]), headers: { "content-type": req.headers["content-type"] }, body });
|
|
23
|
+
for (const e of rc.errors) logs?.appendEvent?.({ ts: Date.now(), type: "plugin-hook-error", hook: "request:received", plugin: e.plugin, error: e.error });
|
|
24
|
+
const respond = rc.value?.respond;
|
|
25
|
+
if (respond && typeof respond === "object") {
|
|
26
|
+
return json(res, respond.status || 200, respond.body ?? {});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const startedAt = Date.now();
|
|
31
|
+
const reqId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
32
|
+
const perf0 = performance.now();
|
|
33
|
+
const stages = [];
|
|
34
|
+
const mark = (name) => stages.push([name, Math.round(performance.now() - perf0)]);
|
|
35
|
+
const hops = parseHops(req.headers["x-mslxdff-hops"]);
|
|
36
|
+
const lockModel = req.headers["x-mslxdff-model-lock"] || "";
|
|
37
|
+
const rawModel = body.model || "";
|
|
38
|
+
const requested = normalizeModel(lockModel || rawModel || "");
|
|
39
|
+
const useAuto = isAutoModel(requested);
|
|
40
|
+
mark("parsed");
|
|
41
|
+
|
|
42
|
+
let order;
|
|
43
|
+
if (lockModel) {
|
|
44
|
+
order = [requested];
|
|
45
|
+
} else if (useAuto) {
|
|
46
|
+
order = auto ? await auto.candidates() : [""];
|
|
47
|
+
} else {
|
|
48
|
+
order = auto ? await auto.candidatesFor(requested) : [requested];
|
|
49
|
+
}
|
|
50
|
+
if (!order.length) order = [""];
|
|
51
|
+
const canFallback = order.length > 1;
|
|
52
|
+
const canForwardPeers = Boolean(peers) && hops < maxHops;
|
|
53
|
+
mark("ordered");
|
|
54
|
+
|
|
55
|
+
const logCall = (model, status) =>
|
|
56
|
+
logs?.appendCall({ reqId, model, auto: useAuto, status, durationMs: Date.now() - startedAt, stream: Boolean(body.stream), stages });
|
|
57
|
+
const logError = (model, status, message) =>
|
|
58
|
+
logs?.appendError({ reqId, model, auto: useAuto, status, message, stages });
|
|
59
|
+
const evt = (type, data) => {
|
|
60
|
+
const entry = { ts: Date.now(), reqId, type, ...data, model: data.model ?? requested, auto: useAuto, durationMs: Date.now() - startedAt, stages: [...stages] };
|
|
61
|
+
if (bus) bus.emit(entry);
|
|
62
|
+
logs?.appendEvent?.(entry);
|
|
63
|
+
};
|
|
64
|
+
const done = (info) => {
|
|
65
|
+
if (!plugins?.length) return;
|
|
66
|
+
runHook(plugins, "request:completed", { reqId, requested, useAuto, hops, stream: Boolean(body.stream), durationMs: Date.now() - startedAt, ...info }).catch(() => {});
|
|
67
|
+
};
|
|
68
|
+
evt("request", { reqId, hops, ip: clientIp(req), stream: Boolean(body.stream), prompt: summarizePrompt(body), rawModel, requested, lockModel: lockModel || null });
|
|
69
|
+
evt("ordered", { reqId, order, canFallback, canForwardPeers, useAuto, statuses: auto?.statuses?.() ?? null });
|
|
70
|
+
|
|
71
|
+
if (plugins?.length && !lockModel) {
|
|
72
|
+
const sel = await runHook(plugins, "model:select", { reqId, requested, useAuto, order: [...order], hops, stream: Boolean(body.stream) });
|
|
73
|
+
if (sel.changed && Array.isArray(sel.value) && sel.value.length) {
|
|
74
|
+
order = sel.value.filter(Boolean);
|
|
75
|
+
if (!order.length) order = [requested];
|
|
76
|
+
evt("plugin-hook", { reqId, hook: "model:select", applied: true, order: [...order] });
|
|
77
|
+
}
|
|
78
|
+
for (const e of sel.errors) evt("plugin-hook-error", { reqId, hook: "model:select", plugin: e.plugin, error: e.error });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const handlerCtx = { reqId, model: null, body, hops, peers, plugins, evt, logError, logCall };
|
|
82
|
+
|
|
83
|
+
let lastErr = null;
|
|
84
|
+
for (let idx = 0; idx < order.length; idx++) {
|
|
85
|
+
const model = order[idx];
|
|
86
|
+
handlerCtx.model = model;
|
|
87
|
+
evt("model-try", { reqId, model, idx, remaining: order.length - idx });
|
|
88
|
+
if (plugins?.length) {
|
|
89
|
+
const bt = await runHook(plugins, "model:beforeTry", { reqId, requested, model, idx, hops });
|
|
90
|
+
for (const e of bt.errors) evt("plugin-hook-error", { reqId, hook: "model:beforeTry", plugin: e.plugin, error: e.error });
|
|
91
|
+
if (bt.value === false || bt.value?.skip === true) {
|
|
92
|
+
evt("plugin-hook", { reqId, hook: "model:beforeTry", applied: true, skipped: model });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let upRes = null;
|
|
97
|
+
let forwarded = { ...injectReasoningContent(model, body), model };
|
|
98
|
+
if (plugins?.length) {
|
|
99
|
+
const ur = await runHook(plugins, "upstream:request", { reqId, requested, model, payload: forwarded, stream: Boolean(body.stream) });
|
|
100
|
+
for (const e of ur.errors) evt("plugin-hook-error", { reqId, hook: "upstream:request", plugin: e.plugin, error: e.error });
|
|
101
|
+
if (ur.changed && ur.value?.payload && typeof ur.value.payload === "object") {
|
|
102
|
+
forwarded = ur.value.payload;
|
|
103
|
+
evt("plugin-hook", { reqId, hook: "upstream:request", applied: true, model, rewrittenModel: forwarded.model ?? null });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const tUp = performance.now();
|
|
107
|
+
evt("upstream-try", { reqId, model, attempt: idx + 1 });
|
|
108
|
+
try {
|
|
109
|
+
upRes = await upstream.chat(forwarded);
|
|
110
|
+
evt("upstream-done", { reqId, model, ok: !(upRes instanceof Error) && upRes.status < 400, status: upRes instanceof Error ? null : upRes.status, timing: upRes._t ?? null, error: null });
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (auto) await auto.recordError(model, { message: errMsg(err) });
|
|
113
|
+
lastErr = { model, upstream: null, status: 502, message: errMsg(err) };
|
|
114
|
+
logError(model, 502, errMsg(err));
|
|
115
|
+
evt("upstream-error", { reqId, model, status: 502, message: errMsg(err), timing: err._t ?? { attempts: [], waitMs: 0, totalMs: Math.round(performance.now() - tUp) } });
|
|
116
|
+
}
|
|
117
|
+
if (plugins?.length) {
|
|
118
|
+
runHook(plugins, "upstream:response", {
|
|
119
|
+
reqId, requested, model,
|
|
120
|
+
status: upRes instanceof Error ? null : upRes instanceof Object ? (upRes.status ?? null) : null,
|
|
121
|
+
ok: !(upRes instanceof Error) && upRes ? upRes.status < 400 : false,
|
|
122
|
+
error: upRes instanceof Error ? errMsg(upRes) : null,
|
|
123
|
+
timing: upRes?._t ?? null,
|
|
124
|
+
}).catch(() => {});
|
|
125
|
+
}
|
|
126
|
+
mark(`up-${model}`);
|
|
127
|
+
if (upRes && upRes.status >= 400) {
|
|
128
|
+
if (auto) await auto.recordError(model, { status: upRes.status });
|
|
129
|
+
lastErr = { model, upstream: upRes, status: upRes.status, message: null };
|
|
130
|
+
logError(model, upRes.status, `upstream ${upRes.status}`);
|
|
131
|
+
evt("upstream-error", { reqId, model, status: upRes.status, message: null, timing: upRes._t ?? null });
|
|
132
|
+
upRes = null;
|
|
133
|
+
}
|
|
134
|
+
if (upRes) {
|
|
135
|
+
const isStream = Boolean(body.stream);
|
|
136
|
+
const d = hedgeDelayMs();
|
|
137
|
+
const hasPeers = Boolean(peers) && peers.ordered().length > 0;
|
|
138
|
+
const doHedge = shouldHedge({ isStream, canForwardPeers, hedgeDelayMs: d, hasPeers }) && upRes.status === 200 && upRes.body;
|
|
139
|
+
if (doHedge) {
|
|
140
|
+
const hr = await handleHedge({ upRes, model, body, order, idx, lastErr, requested, useAuto, lockModel, auto, peers, handlerCtx, evt, logCall, logError, mark, perf0, stages, startedAt, plugins, res, hedgeDelayMs: d });
|
|
141
|
+
if (hr.handled) return;
|
|
142
|
+
if (hr.lastErr) lastErr = hr.lastErr;
|
|
143
|
+
if (hr.upRes === null) upRes = null;
|
|
144
|
+
else if (hr.upRes) upRes = hr.upRes;
|
|
145
|
+
if (upRes === null) {
|
|
146
|
+
// hedge 触发后走下面的 peer/broadband 分支,不再走本地串行
|
|
147
|
+
} else {
|
|
148
|
+
// hedge 未决出胜负,回退到串行本地
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (upRes) {
|
|
152
|
+
const lr = await handleLocalRelay({ upRes, model, body, order, idx, lastErr, requested, useAuto, lockModel, auto, handlerCtx, evt, logCall, logError, mark, perf0, stages, startedAt, plugins, res });
|
|
153
|
+
if (lr.handled) return;
|
|
154
|
+
if (lr.lastErr) { lastErr = lr.lastErr; continue; }
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (canForwardPeers) {
|
|
160
|
+
const pr = await handlePeerRelay({ model, body, lastErr, requested, useAuto, lockModel, auto, peers, handlerCtx, evt, logCall, mark, perf0, stages, startedAt, plugins, res });
|
|
161
|
+
if (pr.handled) return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (groups) {
|
|
165
|
+
const br = await handleBroadbandRelay({ model, body, hops, lastErr, requested, useAuto, lockModel, auto, groups, token, bus, logs, handlerCtx, evt, mark, perf0, stages, res, startedAt, plugins });
|
|
166
|
+
if (br.handled) return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (canFallback) {
|
|
170
|
+
evt("fallback", { reqId, from: model, to: order[idx + 1] ?? null, reason: lastErr?.message || `upstream ${lastErr?.status ?? 502}` });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
await handleExhaustedLocal({ res, body, lastErr, order, handlerCtx: { ...handlerCtx, model, reqId }, evt, logCall, mark, perf0, stages, done, requested, useAuto });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await handleExhaustedAll({ res, body, lastErr, order, requested, handlerCtx: { ...handlerCtx, reqId, startedAt }, evt, logCall, mark, perf0, stages });
|
|
178
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { buildFallbackInfo } from "../fallback.js";
|
|
2
|
+
import { relay, SLOW_TOTAL_MS, STREAM_TIMEOUT_MS, STALL_TIMEOUT_MS, SCORE_STALL_MS } from "../stream.js";
|
|
3
|
+
import { runHook } from "../../plugins.js";
|
|
4
|
+
import { performance } from "node:perf_hooks";
|
|
5
|
+
|
|
6
|
+
export async function handleLocalRelay({
|
|
7
|
+
upRes,
|
|
8
|
+
model,
|
|
9
|
+
body,
|
|
10
|
+
order,
|
|
11
|
+
idx,
|
|
12
|
+
lastErr,
|
|
13
|
+
requested,
|
|
14
|
+
useAuto,
|
|
15
|
+
lockModel,
|
|
16
|
+
auto,
|
|
17
|
+
handlerCtx,
|
|
18
|
+
evt,
|
|
19
|
+
logCall,
|
|
20
|
+
logError,
|
|
21
|
+
mark,
|
|
22
|
+
perf0,
|
|
23
|
+
stages,
|
|
24
|
+
startedAt,
|
|
25
|
+
plugins,
|
|
26
|
+
res,
|
|
27
|
+
}) {
|
|
28
|
+
logCall(model, upRes.status);
|
|
29
|
+
const fallback = buildFallbackInfo({ requested, actual: model, lastErr, via: "local", useAuto, lockModel });
|
|
30
|
+
if (fallback?.fallback) evt("fallback-notice", { reqId: handlerCtx.reqId, requested, actual: model, reason: fallback.reason, notice: fallback.notice, via: "local" });
|
|
31
|
+
evt("relay-start", { reqId: handlerCtx.reqId, model, via: "local", isStream: Boolean(body.stream), fallback });
|
|
32
|
+
const out = await relay(res, upRes, body, {
|
|
33
|
+
fallback,
|
|
34
|
+
onFirstChunk: (delta) => {
|
|
35
|
+
mark(`ttf-${model}`);
|
|
36
|
+
evt("relay-first-chunk", { reqId: handlerCtx.reqId, model, ttfMs: delta });
|
|
37
|
+
if (plugins?.length) runHook(plugins, "relay:first-chunk", { reqId: handlerCtx.reqId, requested, model, via: "local", ttfMs: delta }).catch(() => {});
|
|
38
|
+
},
|
|
39
|
+
onDownstreamAbort: () => {
|
|
40
|
+
evt("client-abort", { reqId: handlerCtx.reqId, model, totalMs: Math.round(performance.now() - perf0), stages: [...stages] });
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
evt("relay-done", { reqId: handlerCtx.reqId, model, via: "local", status: out.status, ttfMs: out.ttfMs, totalMs: out.totalMs, aborted: out.aborted, interrupted: out.interrupted ?? false, detail: out.detail ?? null });
|
|
44
|
+
if (out.status === STREAM_TIMEOUT_MS) {
|
|
45
|
+
if (auto) await auto.recordError(model, { status: 502, slow: true, note: `stream timeout ${STREAM_TIMEOUT_MS}ms` });
|
|
46
|
+
const err = { model, upstream: null, status: 502, message: `stream timed out after ${STREAM_TIMEOUT_MS}ms` };
|
|
47
|
+
logError(model, 502, `stream timeout ${STREAM_TIMEOUT_MS}ms`);
|
|
48
|
+
evt("upstream-error", { reqId: handlerCtx.reqId, model, status: 502, message: "stream timeout", timing: null });
|
|
49
|
+
evt("fallback", { reqId: handlerCtx.reqId, from: model, to: order[idx + 1] ?? null, reason: "stream timeout" });
|
|
50
|
+
return { handled: false, upRes: null, lastErr: err };
|
|
51
|
+
}
|
|
52
|
+
if (out.interrupted) {
|
|
53
|
+
if (auto) {
|
|
54
|
+
await auto.recordError(model, { status: 200, slow: true, note: `stall ${STALL_TIMEOUT_MS}ms` });
|
|
55
|
+
await auto.recordLatency(model, out.totalMs ?? (Date.now() - startedAt));
|
|
56
|
+
}
|
|
57
|
+
evt("slow-model", { model, elapsedMs: out.totalMs ?? (Date.now() - startedAt), threshold: STALL_TIMEOUT_MS, interrupted: true, detail: out.detail ?? null });
|
|
58
|
+
logCall(model, 200);
|
|
59
|
+
evt("result", { model, status: out.status, via: "local", timing: upRes._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, interrupted: true, detail: out.detail ?? null, fallback, requested, actual: model });
|
|
60
|
+
evt("client-response", { requested, actual: model, via: "local", fallback, status: out.status, interrupted: true, reqId: handlerCtx.reqId });
|
|
61
|
+
if (plugins?.length) runHook(plugins, "request:completed", { reqId: handlerCtx.reqId, requested, useAuto, hops: handlerCtx.hops, stream: Boolean(body.stream), durationMs: Date.now() - startedAt, via: "local", status: out.status, actual: model, interrupted: true, fallback }).catch(() => {});
|
|
62
|
+
return { handled: true };
|
|
63
|
+
}
|
|
64
|
+
const elapsed = Date.now() - startedAt;
|
|
65
|
+
const latencyMs = out.totalMs ?? elapsed;
|
|
66
|
+
let scoredSlow = false;
|
|
67
|
+
if (SLOW_TOTAL_MS && auto && elapsed > SLOW_TOTAL_MS && out.status === 200) {
|
|
68
|
+
void auto.recordError(model, { status: 200, slow: true, note: `slow ${elapsed}ms` });
|
|
69
|
+
void auto.recordLatency(model, latencyMs);
|
|
70
|
+
evt("slow-model", { model, elapsedMs: elapsed, threshold: SLOW_TOTAL_MS, reason: "total", detail: out.detail ?? null });
|
|
71
|
+
scoredSlow = true;
|
|
72
|
+
}
|
|
73
|
+
if (out.detail?.stallHits > 0 && auto && out.status === 200) {
|
|
74
|
+
void auto.recordError(model, { status: 200, slow: true, note: `stall ${out.detail.stallHits}x gap>${SCORE_STALL_MS}ms maxGap ${out.detail.maxGapMs}ms` });
|
|
75
|
+
void auto.recordLatency(model, latencyMs);
|
|
76
|
+
evt("slow-model", { model, elapsedMs: elapsed, threshold: SCORE_STALL_MS, reason: "stall", stallHits: out.detail.stallHits, maxGapMs: out.detail.maxGapMs, detail: out.detail ?? null });
|
|
77
|
+
scoredSlow = true;
|
|
78
|
+
}
|
|
79
|
+
if (!scoredSlow && auto && out.status === 200) {
|
|
80
|
+
await auto.recordOk(model, { latencyMs });
|
|
81
|
+
} else if (!scoredSlow && auto) {
|
|
82
|
+
await auto.recordLatency(model, latencyMs);
|
|
83
|
+
}
|
|
84
|
+
evt("result", { model, status: out.status, via: "local", timing: upRes._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, detail: out.detail ?? null, fallback, requested, actual: model });
|
|
85
|
+
evt("client-response", { requested, actual: model, via: "local", fallback, status: out.status, reqId: handlerCtx.reqId });
|
|
86
|
+
if (plugins?.length) runHook(plugins, "request:completed", { reqId: handlerCtx.reqId, requested, useAuto, hops: handlerCtx.hops, stream: Boolean(body.stream), durationMs: Date.now() - startedAt, via: "local", status: out.status, actual: model, fallback }).catch(() => {});
|
|
87
|
+
return { handled: true };
|
|
88
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { buildFallbackInfo } from "../fallback.js";
|
|
2
|
+
import { relay, SLOW_TOTAL_MS } from "../stream.js";
|
|
3
|
+
import { racePeerCandidates } from "../peers.js";
|
|
4
|
+
import { runHook } from "../../plugins.js";
|
|
5
|
+
|
|
6
|
+
export async function handlePeerRelay({
|
|
7
|
+
model,
|
|
8
|
+
body,
|
|
9
|
+
lastErr,
|
|
10
|
+
requested,
|
|
11
|
+
useAuto,
|
|
12
|
+
lockModel,
|
|
13
|
+
auto,
|
|
14
|
+
peers,
|
|
15
|
+
handlerCtx,
|
|
16
|
+
evt,
|
|
17
|
+
logCall,
|
|
18
|
+
mark,
|
|
19
|
+
perf0,
|
|
20
|
+
stages,
|
|
21
|
+
startedAt,
|
|
22
|
+
plugins,
|
|
23
|
+
res,
|
|
24
|
+
}) {
|
|
25
|
+
evt("peer-race-start", { reqId: handlerCtx.reqId, model, peers: peers.ordered().length });
|
|
26
|
+
const win =
|
|
27
|
+
(await racePeerCandidates(peers.ordered(), handlerCtx)) ||
|
|
28
|
+
(await racePeerCandidates(peers.orderedByLastError(), handlerCtx));
|
|
29
|
+
if (!win) {
|
|
30
|
+
evt("peer-race-lose", { reqId: handlerCtx.reqId, model });
|
|
31
|
+
return { handled: false };
|
|
32
|
+
}
|
|
33
|
+
evt("peer-race-win", { reqId: handlerCtx.reqId, model, winPeer: win.peer.url, winTarget: win.target, latencyMs: win.latencyMs });
|
|
34
|
+
await peers.recordResult(win.peer.url, { ok: true, latencyMs: win.latencyMs, model: win.target });
|
|
35
|
+
logCall(win.target, win.res.status);
|
|
36
|
+
const peerFallback = buildFallbackInfo({ requested, actual: win.target, lastErr, via: "peer", useAuto, lockModel });
|
|
37
|
+
if (peerFallback?.fallback) evt("fallback-notice", { reqId: handlerCtx.reqId, requested, actual: win.target, reason: peerFallback.reason, notice: peerFallback.notice, via: "peer" });
|
|
38
|
+
evt("relay-start", { reqId: handlerCtx.reqId, model: win.target, via: "peer", isStream: Boolean(body.stream), fallback: peerFallback });
|
|
39
|
+
const out = await relay(res, win.res, body, {
|
|
40
|
+
fallback: peerFallback,
|
|
41
|
+
onFirstChunk: (d) => mark(`ttf-peer-${win.target}`),
|
|
42
|
+
onDownstreamAbort: () => evt("client-abort", { reqId: handlerCtx.reqId, model: win.target, totalMs: Math.round(Date.now() - startedAt), stages: [...stages] }),
|
|
43
|
+
});
|
|
44
|
+
evt("relay-done", { reqId: handlerCtx.reqId, model: win.target, via: "peer", status: out.status, ttfMs: out.ttfMs, totalMs: out.totalMs, aborted: out.aborted, interrupted: out.interrupted ?? false, detail: out.detail ?? null });
|
|
45
|
+
if (auto && out.status === 200) {
|
|
46
|
+
const latencyMs = out.totalMs ?? win.latencyMs;
|
|
47
|
+
if (out.detail?.stallHits > 0 || (latencyMs && latencyMs > SLOW_TOTAL_MS)) {
|
|
48
|
+
void auto.recordError(win.target, { status: 200, slow: true, note: `peer slow ${latencyMs}ms` });
|
|
49
|
+
void auto.recordLatency(win.target, latencyMs);
|
|
50
|
+
} else {
|
|
51
|
+
await auto.recordOk(win.target, { latencyMs });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
evt("result", { model: win.target, status: out.status, via: "peer", timing: win.res._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, detail: out.detail ?? null, fallback: peerFallback, requested, actual: win.target });
|
|
55
|
+
evt("client-response", { requested, actual: win.target, via: "peer", fallback: peerFallback, status: out.status, reqId: handlerCtx.reqId });
|
|
56
|
+
if (plugins?.length) runHook(plugins, "request:completed", { reqId: handlerCtx.reqId, requested, useAuto, hops: handlerCtx.hops, stream: Boolean(body.stream), durationMs: Date.now() - startedAt, via: "peer", status: out.status, actual: win.target, fallback: peerFallback }).catch(() => {});
|
|
57
|
+
return { handled: true };
|
|
58
|
+
}
|