mslxdff 0.1.199 → 0.1.203
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 +3 -3
- package/bin/mslxdff.js +1 -1
- package/package.json +38 -38
- package/plugins/README.md +14 -14
- package/plugins/prefer-model.mjs.example +14 -14
- package/src/anthropic/count-tokens.js +61 -61
- package/src/anthropic/request-body.js +126 -126
- package/src/anthropic/shared.js +18 -18
- package/src/anthropic/sse-aggregate.js +56 -56
- package/src/anthropic/translate.js +287 -287
- package/src/anthropic/web-search.js +361 -361
- package/src/api-kind.js +31 -31
- package/src/autostart.js +288 -288
- package/src/bench/probe.js +1 -1
- package/src/bench/runner.js +1 -1
- package/src/bench/via-probe.js +1 -1
- package/src/bench/via.js +144 -144
- package/src/bench/workbuddy-bench.js +1 -1
- package/src/chat/config.js +11 -11
- package/src/chat/cooling.js +81 -81
- package/src/chat/direct.js +58 -58
- package/src/chat/index.js +6 -6
- package/src/chat/spinner.js +38 -38
- package/src/chat/sse.js +69 -69
- package/src/chat/stats.js +247 -247
- package/src/chat/store.js +48 -48
- package/src/chat/tool-handlers.js +67 -67
- package/src/chat/tools.js +1 -1
- package/src/chat/upstream.js +59 -59
- package/src/chat-pipeline/auto-race.js +158 -158
- package/src/chat-pipeline/empty-turn.js +147 -147
- package/src/chat-pipeline/engine.js +17 -17
- package/src/chat-pipeline/serial-trial.js +283 -283
- package/src/chat-pipeline/terminal-verdict.js +147 -147
- package/src/chooser.js +54 -54
- package/src/claude-desktop/apply.js +101 -101
- package/src/claude-desktop/context1m.js +38 -38
- package/src/claude-desktop/doctor.js +146 -146
- package/src/claude-desktop/slots.js +152 -152
- package/src/cli/bootstrap.js +1 -1
- package/src/cli/commands/claude-desktop.js +284 -284
- package/src/cli/commands/daemon.js +231 -231
- package/src/cli/commands/model/list-live.js +22 -22
- package/src/cli/commands/model/list-providers.js +76 -76
- package/src/cli/commands/model/list-render.js +77 -77
- package/src/cli/commands/model/list-sort.js +52 -52
- package/src/cli/commands/model/list-tty.js +76 -76
- package/src/cli/commands/model/list.js +166 -166
- package/src/cli/commands/model/live-models.js +24 -24
- package/src/cli/commands/model/stats.js +43 -43
- package/src/cli/commands/model/status.js +47 -47
- package/src/cli/commands/model.js +51 -51
- package/src/cli/commands/provider/add.js +108 -108
- package/src/cli/commands/provider/allowlist.js +99 -99
- package/src/cli/commands/provider/cline-free.js +99 -99
- package/src/cli/commands/provider/cline-login.js +218 -218
- package/src/cli/commands/provider/cline-quota.js +65 -65
- package/src/cli/commands/provider/codearts-login.js +45 -45
- package/src/cli/commands/provider/config.js +119 -119
- package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
- package/src/cli/commands/provider/keys-guard.js +50 -50
- package/src/cli/commands/provider/keys.js +161 -161
- package/src/cli/commands/provider/qoder-checkin.js +81 -81
- package/src/cli/commands/provider/qoder-login.js +102 -102
- package/src/cli/commands/provider/raccoon-login.js +180 -180
- package/src/cli/commands/provider/raccoon-password.js +162 -162
- package/src/cli/commands/provider/raccoon-quota.js +109 -109
- package/src/cli/commands/provider/traework-checkin.js +144 -144
- package/src/cli/commands/provider/traework-login.js +164 -164
- package/src/cli/commands/provider/workbuddy-login.js +153 -153
- package/src/cli/commands/provider/zcode-login.js +77 -77
- package/src/cli/commands/provider/zcode-quota.js +55 -55
- package/src/cli/commands/provider.js +1 -1
- package/src/cli/commands/stats.js +149 -149
- package/src/cli/commands/system.js +261 -261
- package/src/cli/commands/timezone.js +47 -47
- package/src/cli/commands/use-group.js +60 -60
- package/src/cli/commands/workbuddy.js +189 -189
- package/src/cli/format.js +141 -141
- package/src/cli/group-helpers.js +70 -70
- package/src/cli/help.js +80 -80
- package/src/cli/index.js +70 -70
- package/src/cli/interactive.js +121 -121
- package/src/cli/join-core.js +99 -99
- package/src/cli/join-wizard.js +175 -175
- package/src/cli/policy.js +136 -136
- package/src/cli/provider-row.js +126 -126
- package/src/cli/util.js +24 -24
- package/src/compat.js +53 -53
- package/src/events.js +38 -38
- package/src/free-lane.js +131 -131
- package/src/free-watcher.js +63 -63
- package/src/groups.js +262 -262
- package/src/logs.js +195 -195
- package/src/metrics.js +62 -62
- package/src/model-capabilities/context-lookup.js +65 -65
- package/src/model-capabilities/merge.js +142 -142
- package/src/model-trace.js +187 -187
- package/src/models.js +1 -1
- package/src/opencode-identity.js +72 -72
- package/src/peers.js +254 -251
- package/src/plugins.js +107 -107
- package/src/providers/base.js +164 -164
- package/src/providers/classify.js +22 -22
- package/src/providers/cline/allowlist-sync.js +93 -93
- package/src/providers/cline/free-catalog.js +71 -71
- package/src/providers/cline/headers.js +37 -37
- package/src/providers/cline/index.js +130 -130
- package/src/providers/cline/models.js +150 -150
- package/src/providers/cline/usage.js +274 -274
- package/src/providers/codearts/auth-pool.js +148 -148
- package/src/providers/codearts/concurrency.js +34 -34
- package/src/providers/codearts/const.js +65 -65
- package/src/providers/codearts/dpop.js +95 -95
- package/src/providers/codearts/index.js +175 -175
- package/src/providers/codearts/login.js +214 -214
- package/src/providers/codearts/models.js +182 -182
- package/src/providers/codearts/sign.js +96 -96
- package/src/providers/codearts/sse.js +204 -204
- package/src/providers/codearts/stream.js +209 -209
- package/src/providers/codearts/sts.js +124 -124
- package/src/providers/codearts.js +1 -1
- package/src/providers/dispatcher.js +146 -146
- package/src/providers/generic.js +156 -156
- package/src/providers/globalqwenwork/account-store.js +141 -141
- package/src/providers/globalqwenwork/constants.js +73 -73
- package/src/providers/globalqwenwork/crypto.js +2 -2
- package/src/providers/globalqwenwork/http.js +2 -2
- package/src/providers/globalqwenwork/index.js +331 -331
- package/src/providers/globalqwenwork/payload.js +147 -147
- package/src/providers/globalqwenwork/rsa.js +2 -2
- package/src/providers/globalqwenwork/sse.js +2 -2
- package/src/providers/globalqwenwork/stream.js +2 -2
- package/src/providers/globalqwenwork.js +1 -1
- package/src/providers/globalworkbuddy/account-store.js +198 -198
- package/src/providers/globalworkbuddy/auth.js +97 -97
- package/src/providers/globalworkbuddy/catalog.js +138 -138
- package/src/providers/globalworkbuddy/constants.js +73 -73
- package/src/providers/globalworkbuddy/device-auth.js +120 -120
- package/src/providers/globalworkbuddy/errors.js +98 -98
- package/src/providers/globalworkbuddy/headers.js +89 -89
- package/src/providers/globalworkbuddy/models.js +96 -96
- package/src/providers/globalworkbuddy/payload.js +80 -80
- package/src/providers/index.js +5 -5
- package/src/providers/model-id.js +172 -172
- package/src/providers/opencode.js +19 -19
- package/src/providers/openrouter.js +31 -31
- package/src/providers/qoder/account-store.js +106 -106
- package/src/providers/qoder/aggregate.js +60 -60
- package/src/providers/qoder/baseprompt.json +591 -591
- package/src/providers/qoder/checkin.js +160 -160
- package/src/providers/qoder/constants.js +44 -44
- package/src/providers/qoder/encode.js +41 -41
- package/src/providers/qoder/fingerprint.js +38 -38
- package/src/providers/qoder/models.js +77 -77
- package/src/providers/qoder/oauth.js +84 -84
- package/src/providers/qoder/payload.js +162 -162
- package/src/providers/qoder/request.js +27 -27
- package/src/providers/qoder/session.js +134 -134
- package/src/providers/qoder/sticky.js +38 -38
- package/src/providers/qoder/stream.js +116 -116
- package/src/providers/qoder.js +1 -1
- package/src/providers/raccoon/account-store.js +216 -216
- package/src/providers/raccoon/auth.js +67 -67
- package/src/providers/raccoon/chat.js +228 -228
- package/src/providers/raccoon/const.js +180 -165
- package/src/providers/raccoon/credits.js +185 -175
- package/src/providers/raccoon/crypto.js +43 -0
- package/src/providers/raccoon/envelope.js +31 -31
- package/src/providers/raccoon/headers.js +45 -45
- package/src/providers/raccoon/index.js +216 -216
- package/src/providers/raccoon/login.js +279 -279
- package/src/providers/raccoon/models.js +160 -160
- package/src/providers/raccoon/password-login.js +102 -87
- package/src/providers/raccoon/qr-codewords.js +115 -115
- package/src/providers/raccoon/qr.js +236 -236
- package/src/providers/raccoon/sse.js +135 -135
- package/src/providers/responses-channel.js +120 -120
- package/src/providers/share-keys.js +53 -53
- package/src/providers/traework/account-store.js +234 -234
- package/src/providers/traework/chat.js +217 -217
- package/src/providers/traework/checkin.js +77 -77
- package/src/providers/traework/constants.js +41 -41
- package/src/providers/traework/errors.js +52 -52
- package/src/providers/traework/headers.js +79 -79
- package/src/providers/traework/index.js +132 -132
- package/src/providers/traework/market-identity.js +116 -116
- package/src/providers/traework/models.js +98 -98
- package/src/providers/traework/payload.js +86 -86
- package/src/providers/traework/sse.js +184 -184
- package/src/providers/traework/token.js +52 -52
- package/src/providers/traework.js +2 -2
- package/src/providers/workbuddy/account-store.js +186 -186
- package/src/providers/workbuddy/auth.js +158 -158
- package/src/providers/workbuddy/balance.js +86 -86
- package/src/providers/workbuddy/cat-travel.js +125 -125
- package/src/providers/workbuddy/checkin.js +75 -75
- package/src/providers/workbuddy/desktop-info.js +78 -78
- package/src/providers/workbuddy/growth-api.js +67 -67
- package/src/providers/workbuddy/growth-plans.js +63 -63
- package/src/providers/workbuddy/growth.js +181 -181
- package/src/providers/workbuddy/index.js +217 -217
- package/src/providers/workbuddy/models.js +126 -126
- package/src/providers/workbuddy/payload.js +141 -141
- package/src/providers/workbuddy/rotation-log.js +83 -83
- package/src/providers/workbuddy/sanitize-tools.js +61 -61
- package/src/providers/workbuddy/sdk-chat.js +26 -26
- package/src/providers/workbuddy-balance.js +78 -78
- package/src/providers/zcode/account-store.js +129 -129
- package/src/providers/zcode/auth.js +28 -28
- package/src/providers/zcode/chat.js +179 -179
- package/src/providers/zcode/const.js +54 -54
- package/src/providers/zcode/context-shape.js +162 -162
- package/src/providers/zcode/headers.js +93 -93
- package/src/providers/zcode/index.js +124 -124
- package/src/providers/zcode/models.js +65 -65
- package/src/providers/zcode/oauth.js +120 -120
- package/src/providers/zcode/quota.js +176 -176
- package/src/providers/zcode/sse.js +196 -196
- package/src/readline-compat.js +25 -25
- package/src/responses/translate.js +311 -311
- package/src/routes/chat/broadband-handler.js +72 -72
- package/src/routes/chat/empty-turn-report.js +31 -31
- package/src/routes/chat/exhausted-handler.js +87 -87
- package/src/routes/chat/hedge-handler.js +130 -130
- package/src/routes/chat/index.js +1 -1
- package/src/routes/chat/local-handler.js +63 -63
- package/src/routes/chat/peer-handler.js +81 -81
- package/src/routes/chat.js +3 -3
- package/src/routes/fallback.js +88 -88
- package/src/routes/groups-relay.js +214 -214
- package/src/routes/groups.js +122 -122
- package/src/routes/helpers.js +83 -83
- package/src/routes/index.js +147 -147
- package/src/routes/messages-route.js +365 -365
- package/src/routes/peers.js +23 -1
- package/src/routes/relay-queue.js +181 -181
- package/src/routes/relay.js +60 -60
- package/src/routes/responses-route.js +213 -213
- package/src/routes/stream-body.js +84 -84
- package/src/routes/stream-hold.js +337 -337
- package/src/routes/stream-scan.js +366 -366
- package/src/routes.js +3 -3
- package/src/runtime/auto-update.js +113 -113
- package/src/runtime/bootstrap.js +8 -0
- package/src/runtime/broadband-stream.js +79 -79
- package/src/runtime/broadband.js +103 -103
- package/src/runtime/group-sync.js +81 -81
- package/src/runtime/keep-warm.js +120 -0
- package/src/runtime/lifecycle-forensics.js +116 -116
- package/src/runtime/lifecycle-log.js +87 -87
- package/src/runtime/log-stamp.js +73 -73
- package/src/runtime/proc-info.js +137 -137
- package/src/runtime/qoder-checkin.js +93 -93
- package/src/runtime/raccoon-checkin.js +126 -126
- package/src/runtime/server-lifecycle.js +189 -189
- package/src/runtime/traework-checkin.js +118 -118
- package/src/runtime/workbuddy-checkin.js +120 -120
- package/src/runtime/workbuddy-growth.js +133 -133
- package/src/server.js +7 -0
- package/src/state/facade.js +58 -58
- package/src/state/memory.js +162 -162
- package/src/state/merge.js +25 -25
- package/src/state/migrations/cline-unify.js +94 -94
- package/src/state/migrations.js +33 -33
- package/src/state/persist.js +42 -42
- package/src/state/provider-config.js +142 -142
- package/src/state/schemas/group.js +31 -31
- package/src/state/schemas/model.js +94 -94
- package/src/state/schemas/peer.js +31 -31
- package/src/state/schemas/port.js +10 -10
- package/src/state/schemas/provider.js +202 -202
- package/src/state/schemas/timezone.js +70 -70
- package/src/state/schemas/token.js +48 -48
- package/src/state/schemas/use-group.js +102 -102
- package/src/state/store.js +176 -176
- package/src/state.js +1 -1
- package/src/sync-claude-desktop.js +234 -234
- package/src/sync-claude.js +152 -152
- package/src/sync-codex.js +116 -116
- package/src/sync-workbuddy.js +158 -158
- package/src/talk-full-redact.js +129 -129
- package/src/talk-full.js +131 -131
- package/src/talk-log.js +257 -257
- package/src/text-tokens.js +52 -52
- package/src/thinking-effort.js +67 -67
- package/src/time.js +106 -106
- package/src/timeline.js +26 -26
- package/src/transport/index.js +1 -1
- package/src/upstream-engine/index.js +74 -72
- package/src/upstream-engine/mode.js +15 -15
- package/src/upstream-engine/sdk/attempt.js +221 -221
- package/src/upstream-engine/sdk/chat.js +28 -28
- package/src/upstream-engine/sdk/convert.js +192 -192
- package/src/upstream-engine/sdk/diagnose.js +62 -62
- package/src/upstream-engine/sdk/dispatch.js +55 -55
- package/src/upstream-engine/sdk/responses.js +176 -176
- package/src/upstream-engine/sdk/sse.js +166 -166
- package/src/upstream-responses.js +305 -305
- package/src/upstream.js +53 -1
- package/src/usage/record.js +106 -106
- package/src/usage/report.js +195 -195
- package/src/v6pool/claim.js +157 -157
- package/src/v6pool/config.js +65 -65
- package/src/v6pool/detect.js +130 -130
- package/src/v6pool/index.js +152 -152
- package/src/v6pool/prefix.js +140 -140
- package/src/v6pool/probe.js +40 -40
- package/src/v6pool/report.js +104 -104
- package/src/v6pool/rotate.js +263 -239
- package/src/warp-pool/config.js +74 -74
- package/src/warp-pool/health.js +106 -106
- package/src/warp-pool/identity.js +141 -141
- package/src/warp-pool/index.js +358 -358
- package/src/warp-pool/keys.js +29 -29
- package/src/warp-pool/nodes.js +95 -95
- package/src/warp-pool/ports.js +160 -160
- package/src/warp-pool/register.js +113 -113
- package/src/warp-pool/report.js +232 -232
- package/src/warp-pool/revive.js +168 -168
- package/src/warp-pool/rotate.js +214 -214
- package/src/warp-pool/strategy.js +43 -43
- package/src/warp-pool/verdict.js +69 -69
- package/src/warp-pool/wireproxy.js +162 -162
- package/docs/cli_help_mini.md +0 -146
- package/src/providers/AGENTS.md +0 -49
- package/src/routes/AGENTS.md +0 -37
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import { relay, SLOW_TOTAL_MS, STREAM_TIMEOUT_MS, STALL_TIMEOUT_MS, SCORE_STALL_MS } from "../stream.js";
|
|
2
|
-
import { buildFallbackInfo } from "../fallback.js";
|
|
3
|
-
import { createRelayPipeline } from "./relay-pipeline.js";
|
|
4
|
-
import { tryBroadbandRelay } from "../relay-queue.js";
|
|
5
|
-
|
|
6
|
-
/** 薄适配:broadband 两种形态合一经由 pipeline */
|
|
7
|
-
export async function handleBroadbandRelay({
|
|
8
|
-
model,
|
|
9
|
-
body,
|
|
10
|
-
hops,
|
|
11
|
-
lastErr,
|
|
12
|
-
requested,
|
|
13
|
-
useAuto,
|
|
14
|
-
lockModel,
|
|
15
|
-
auto,
|
|
16
|
-
groups,
|
|
17
|
-
token,
|
|
18
|
-
bus,
|
|
19
|
-
logs,
|
|
20
|
-
handlerCtx,
|
|
21
|
-
evt,
|
|
22
|
-
mark,
|
|
23
|
-
perf0,
|
|
24
|
-
attemptStartMs,
|
|
25
|
-
stages,
|
|
26
|
-
res,
|
|
27
|
-
startedAt,
|
|
28
|
-
plugins,
|
|
29
|
-
deps = {},
|
|
30
|
-
}) {
|
|
31
|
-
const { createPipeline = createRelayPipeline, tryBroadbandRelay: tryRelay = tryBroadbandRelay } = deps;
|
|
32
|
-
const bb = await tryRelay({ groups, token, model, body, hops, bus, logs, reqId: handlerCtx.reqId, evt, res, mark, perf0, stages });
|
|
33
|
-
if (!bb) {
|
|
34
|
-
evt("relay-miss", { reqId: handlerCtx.reqId, model });
|
|
35
|
-
return { handled: false };
|
|
36
|
-
}
|
|
37
|
-
const pipeline = createPipeline({
|
|
38
|
-
relay,
|
|
39
|
-
buildFallbackInfo,
|
|
40
|
-
auto,
|
|
41
|
-
plugins,
|
|
42
|
-
evt,
|
|
43
|
-
mark,
|
|
44
|
-
logCall: () => {},
|
|
45
|
-
logError: () => {},
|
|
46
|
-
constants: { STREAM_TIMEOUT_MS, SLOW_TOTAL_MS, STALL_TIMEOUT_MS, SCORE_STALL_MS },
|
|
47
|
-
startedAt,
|
|
48
|
-
stages,
|
|
49
|
-
});
|
|
50
|
-
const isResponse = bb.result && typeof bb.result.status === "number" && typeof bb.result.headers?.get === "function";
|
|
51
|
-
if (isResponse) {
|
|
52
|
-
const r = await pipeline.execute({ res, upRes: bb.result, body, requested, actual: model, lastErr, via: "broadband", lockModel, useAuto, handlerCtx, mark, perf0, attemptStartMs, stages, startedAt });
|
|
53
|
-
if (!r.handled) return { handled: false, upRes: null, lastErr: r.lastErr };
|
|
54
|
-
return { handled: true };
|
|
55
|
-
}
|
|
56
|
-
if (bb.result && typeof bb.result.status === "number") {
|
|
57
|
-
const b = bb.result.body || "";
|
|
58
|
-
const str = typeof b === "string" ? b : JSON.stringify(b);
|
|
59
|
-
const isSSE = bb.result.headers?.["Content-Type"]?.includes("text/event-stream");
|
|
60
|
-
const fakeRes = {
|
|
61
|
-
status: bb.result.status,
|
|
62
|
-
headers: { get: (k) => bb.result.headers?.[k] || bb.result.headers?.[k.toLowerCase()] || null },
|
|
63
|
-
text: async () => str,
|
|
64
|
-
body: isSSE ? (async function* () { yield Buffer.from(str); })() : null,
|
|
65
|
-
};
|
|
66
|
-
const r = await pipeline.execute({ res, upRes: fakeRes, body, requested, actual: model, lastErr, via: "broadband-local", lockModel, useAuto, handlerCtx, mark, perf0, attemptStartMs, stages, startedAt });
|
|
67
|
-
if (!r.handled) return { handled: false, upRes: null, lastErr: r.lastErr };
|
|
68
|
-
return { handled: true };
|
|
69
|
-
}
|
|
70
|
-
evt("relay-miss", { reqId: handlerCtx.reqId, model });
|
|
71
|
-
return { handled: false };
|
|
72
|
-
}
|
|
1
|
+
import { relay, SLOW_TOTAL_MS, STREAM_TIMEOUT_MS, STALL_TIMEOUT_MS, SCORE_STALL_MS } from "../stream.js";
|
|
2
|
+
import { buildFallbackInfo } from "../fallback.js";
|
|
3
|
+
import { createRelayPipeline } from "./relay-pipeline.js";
|
|
4
|
+
import { tryBroadbandRelay } from "../relay-queue.js";
|
|
5
|
+
|
|
6
|
+
/** 薄适配:broadband 两种形态合一经由 pipeline */
|
|
7
|
+
export async function handleBroadbandRelay({
|
|
8
|
+
model,
|
|
9
|
+
body,
|
|
10
|
+
hops,
|
|
11
|
+
lastErr,
|
|
12
|
+
requested,
|
|
13
|
+
useAuto,
|
|
14
|
+
lockModel,
|
|
15
|
+
auto,
|
|
16
|
+
groups,
|
|
17
|
+
token,
|
|
18
|
+
bus,
|
|
19
|
+
logs,
|
|
20
|
+
handlerCtx,
|
|
21
|
+
evt,
|
|
22
|
+
mark,
|
|
23
|
+
perf0,
|
|
24
|
+
attemptStartMs,
|
|
25
|
+
stages,
|
|
26
|
+
res,
|
|
27
|
+
startedAt,
|
|
28
|
+
plugins,
|
|
29
|
+
deps = {},
|
|
30
|
+
}) {
|
|
31
|
+
const { createPipeline = createRelayPipeline, tryBroadbandRelay: tryRelay = tryBroadbandRelay } = deps;
|
|
32
|
+
const bb = await tryRelay({ groups, token, model, body, hops, bus, logs, reqId: handlerCtx.reqId, evt, res, mark, perf0, stages });
|
|
33
|
+
if (!bb) {
|
|
34
|
+
evt("relay-miss", { reqId: handlerCtx.reqId, model });
|
|
35
|
+
return { handled: false };
|
|
36
|
+
}
|
|
37
|
+
const pipeline = createPipeline({
|
|
38
|
+
relay,
|
|
39
|
+
buildFallbackInfo,
|
|
40
|
+
auto,
|
|
41
|
+
plugins,
|
|
42
|
+
evt,
|
|
43
|
+
mark,
|
|
44
|
+
logCall: () => {},
|
|
45
|
+
logError: () => {},
|
|
46
|
+
constants: { STREAM_TIMEOUT_MS, SLOW_TOTAL_MS, STALL_TIMEOUT_MS, SCORE_STALL_MS },
|
|
47
|
+
startedAt,
|
|
48
|
+
stages,
|
|
49
|
+
});
|
|
50
|
+
const isResponse = bb.result && typeof bb.result.status === "number" && typeof bb.result.headers?.get === "function";
|
|
51
|
+
if (isResponse) {
|
|
52
|
+
const r = await pipeline.execute({ res, upRes: bb.result, body, requested, actual: model, lastErr, via: "broadband", lockModel, useAuto, handlerCtx, mark, perf0, attemptStartMs, stages, startedAt });
|
|
53
|
+
if (!r.handled) return { handled: false, upRes: null, lastErr: r.lastErr };
|
|
54
|
+
return { handled: true };
|
|
55
|
+
}
|
|
56
|
+
if (bb.result && typeof bb.result.status === "number") {
|
|
57
|
+
const b = bb.result.body || "";
|
|
58
|
+
const str = typeof b === "string" ? b : JSON.stringify(b);
|
|
59
|
+
const isSSE = bb.result.headers?.["Content-Type"]?.includes("text/event-stream");
|
|
60
|
+
const fakeRes = {
|
|
61
|
+
status: bb.result.status,
|
|
62
|
+
headers: { get: (k) => bb.result.headers?.[k] || bb.result.headers?.[k.toLowerCase()] || null },
|
|
63
|
+
text: async () => str,
|
|
64
|
+
body: isSSE ? (async function* () { yield Buffer.from(str); })() : null,
|
|
65
|
+
};
|
|
66
|
+
const r = await pipeline.execute({ res, upRes: fakeRes, body, requested, actual: model, lastErr, via: "broadband-local", lockModel, useAuto, handlerCtx, mark, perf0, attemptStartMs, stages, startedAt });
|
|
67
|
+
if (!r.handled) return { handled: false, upRes: null, lastErr: r.lastErr };
|
|
68
|
+
return { handled: true };
|
|
69
|
+
}
|
|
70
|
+
evt("relay-miss", { reqId: handlerCtx.reqId, model });
|
|
71
|
+
return { handled: false };
|
|
72
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
// 空转 200 的收场记账(从 relay-pipeline.js 抽出:那儿是 20KB 硬门的存量超标文件,先拆后写)。
|
|
2
|
-
// 只做「把这一轮判成空转后该记什么、该把 lastErr 造成什么形状」,不碰网络/闸门/响应体。
|
|
3
|
-
// 判据本身不在这里:单一真相是 stream-scan.js 的 isEmptyTurnDetail(relay 侧「要不要封口」与
|
|
4
|
-
// 这里「要不要重拉」必须同源,否则会出现留了口却不再救、或已终结却仍重拉的错拍)。
|
|
5
|
-
import { upstreamEcho } from "../../model-trace.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 空转 200 收场:记 errors.log + upstream-error/fallback 事件,返回交给 serial-trial 的 lastErr。
|
|
9
|
-
* @param {{ out: object, actual: string, reqId: string, upRes: object, logError: Function, evt: Function }} p
|
|
10
|
-
* @returns {{ handled: false, upRes: null, lastErr: object }}
|
|
11
|
-
*/
|
|
12
|
-
export function reportEmptyTurn({ out, actual, reqId, upRes, logError, evt }) {
|
|
13
|
-
const d = out.detail || {};
|
|
14
|
-
const why = d.sawFinishReason ? ` (finish_reason=${d.sawFinishReason})` : " (no content, no tool calls)";
|
|
15
|
-
// 错误包络暂扣后下游不再直观看到上游原文:把摘要带进最终报错(截断 200 字),排障不断线。
|
|
16
|
-
const err = d.upstreamErrorText ? ` upstream=${String(d.upstreamErrorText).slice(0, 200)}` : "";
|
|
17
|
-
// 零正文的成因直接写进报错(不只给现象):finish_reason=length + 大量思考 = 额度被 reasoning 吃光
|
|
18
|
-
// (实测 cline-free deepseek reasoningChars=28151、completion_tokens=8192=max_tokens、chars=0)。
|
|
19
|
-
// 同参重拉必复现,故 serial-trial 的空转重试会顺手把 max_tokens 抬一档(见 emptyRaiseCap)。
|
|
20
|
-
const lenCapped = d.sawFinishReason === "length";
|
|
21
|
-
const hint = lenCapped && (Number(d.reasoningChars) || 0) > 0
|
|
22
|
-
? ` [思考 ${Number(d.reasoningChars) || 0} 字刷满 max_tokens,正文 0 字]`
|
|
23
|
-
: "";
|
|
24
|
-
try { logError(actual, 502, `empty turn${why}${err}${hint}`); } catch {}
|
|
25
|
-
evt("upstream-error", { reqId, model: actual, status: 502, message: "empty turn", timing: null, finish: d.sawFinishReason || null, reasoningChars: Number(d.reasoningChars) || 0, wroteFrames: Number(d.wroteChunks) || 0, heldOpen: out.heldOpen === true, ...upstreamEcho(upRes) });
|
|
26
|
-
evt("fallback", { reqId, from: actual, to: null, reason: "empty turn" });
|
|
27
|
-
let errMsg = `EMPTY_MODEL_RESPONSE: upstream returned 200 with no content${why}${err}${hint} — retry or rephrase`;
|
|
28
|
-
// 上游报了 retryAfterSeconds:原样透出(客户端等得起),别被我们的包装话术盖掉。
|
|
29
|
-
if (d.upstreamErrorText && d.upstreamErrorText.includes("retryAfterSeconds")) errMsg = d.upstreamErrorText;
|
|
30
|
-
return { handled: false, upRes: null, lastErr: { model: actual, upstream: null, status: 502, message: errMsg, emptyTurn: true, heldOpen: out.heldOpen === true } };
|
|
31
|
-
}
|
|
1
|
+
// 空转 200 的收场记账(从 relay-pipeline.js 抽出:那儿是 20KB 硬门的存量超标文件,先拆后写)。
|
|
2
|
+
// 只做「把这一轮判成空转后该记什么、该把 lastErr 造成什么形状」,不碰网络/闸门/响应体。
|
|
3
|
+
// 判据本身不在这里:单一真相是 stream-scan.js 的 isEmptyTurnDetail(relay 侧「要不要封口」与
|
|
4
|
+
// 这里「要不要重拉」必须同源,否则会出现留了口却不再救、或已终结却仍重拉的错拍)。
|
|
5
|
+
import { upstreamEcho } from "../../model-trace.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 空转 200 收场:记 errors.log + upstream-error/fallback 事件,返回交给 serial-trial 的 lastErr。
|
|
9
|
+
* @param {{ out: object, actual: string, reqId: string, upRes: object, logError: Function, evt: Function }} p
|
|
10
|
+
* @returns {{ handled: false, upRes: null, lastErr: object }}
|
|
11
|
+
*/
|
|
12
|
+
export function reportEmptyTurn({ out, actual, reqId, upRes, logError, evt }) {
|
|
13
|
+
const d = out.detail || {};
|
|
14
|
+
const why = d.sawFinishReason ? ` (finish_reason=${d.sawFinishReason})` : " (no content, no tool calls)";
|
|
15
|
+
// 错误包络暂扣后下游不再直观看到上游原文:把摘要带进最终报错(截断 200 字),排障不断线。
|
|
16
|
+
const err = d.upstreamErrorText ? ` upstream=${String(d.upstreamErrorText).slice(0, 200)}` : "";
|
|
17
|
+
// 零正文的成因直接写进报错(不只给现象):finish_reason=length + 大量思考 = 额度被 reasoning 吃光
|
|
18
|
+
// (实测 cline-free deepseek reasoningChars=28151、completion_tokens=8192=max_tokens、chars=0)。
|
|
19
|
+
// 同参重拉必复现,故 serial-trial 的空转重试会顺手把 max_tokens 抬一档(见 emptyRaiseCap)。
|
|
20
|
+
const lenCapped = d.sawFinishReason === "length";
|
|
21
|
+
const hint = lenCapped && (Number(d.reasoningChars) || 0) > 0
|
|
22
|
+
? ` [思考 ${Number(d.reasoningChars) || 0} 字刷满 max_tokens,正文 0 字]`
|
|
23
|
+
: "";
|
|
24
|
+
try { logError(actual, 502, `empty turn${why}${err}${hint}`); } catch {}
|
|
25
|
+
evt("upstream-error", { reqId, model: actual, status: 502, message: "empty turn", timing: null, finish: d.sawFinishReason || null, reasoningChars: Number(d.reasoningChars) || 0, wroteFrames: Number(d.wroteChunks) || 0, heldOpen: out.heldOpen === true, ...upstreamEcho(upRes) });
|
|
26
|
+
evt("fallback", { reqId, from: actual, to: null, reason: "empty turn" });
|
|
27
|
+
let errMsg = `EMPTY_MODEL_RESPONSE: upstream returned 200 with no content${why}${err}${hint} — retry or rephrase`;
|
|
28
|
+
// 上游报了 retryAfterSeconds:原样透出(客户端等得起),别被我们的包装话术盖掉。
|
|
29
|
+
if (d.upstreamErrorText && d.upstreamErrorText.includes("retryAfterSeconds")) errMsg = d.upstreamErrorText;
|
|
30
|
+
return { handled: false, upRes: null, lastErr: { model: actual, upstream: null, status: 502, message: errMsg, emptyTurn: true, heldOpen: out.heldOpen === true } };
|
|
31
|
+
}
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
// Note: 失败收尾四分支在 result 后补 client-response,与成功路径对账口径一致(result 条数 == client-response 条数)— 见 .agents/notes/implemented/bug-fix/2026-09-25-sdk-headers-timeout-and-failure-client-response.md
|
|
2
|
-
import { relay } from "../stream.js";
|
|
3
|
-
import { endHeldFailure } from "../stream-hold.js"; // 终局收场出口(502 JSON 或 SSE 错误帧 + [DONE])
|
|
4
|
-
import { json } from "../helpers.js";
|
|
5
|
-
import { performance } from "node:perf_hooks";
|
|
6
|
-
import { recordRelayTalk } from "../../talk-log.js"; // 环形对话日志:exhausted 收尾直连 relay,落盘点必须自己补一份
|
|
7
|
-
import { recordAgentLoop } from "../../talk-full.js"; // agent 回路全量语料(talk/full):与 talk.log 并排各调一次,见 openspec add-agent-loop-capture D1
|
|
8
|
-
import { captureTalkFlush } from "../stream-scan.js"; // 收场回收缺尾换行的末行,record* 读桶前调用(openspec fix-talk-capture-frame-loss D4)
|
|
9
|
-
|
|
10
|
-
// 最后一站的收场:relay 的「不写响应」路径(超时 / 空轮延后封口)都汇到这里,没有上层 failover 了,
|
|
11
|
-
// 必须把响应「恰好一次」地关掉。形状由 endHeldFailure 按 headers 是否已 flush 决定
|
|
12
|
-
// (未 flush → 502 JSON;已 flush 的 SSE → 错误帧 + [DONE]),这里不再自己 json() 打已 flush 的流。
|
|
13
|
-
function terminal(res, message) {
|
|
14
|
-
endHeldFailure(res, message || "all auto models failed");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function handleExhaustedLocal({ res, body, lastErr, order, handlerCtx, evt, logCall, mark, perf0, stages, done, requested, useAuto, deps = {} }) {
|
|
18
|
-
const { relay: relayImpl = relay } = deps;
|
|
19
|
-
const model = lastErr?.model ?? handlerCtx.model;
|
|
20
|
-
evt("exhausted-local", { reqId: handlerCtx.reqId, lastModel: lastErr?.model ?? model, lastStatus: lastErr?.status ?? 502, order });
|
|
21
|
-
logCall(lastErr?.model ?? model, lastErr?.status ?? 502);
|
|
22
|
-
if (lastErr?.upstream) {
|
|
23
|
-
evt("relay-start", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-exhausted", isStream: Boolean(body.stream) });
|
|
24
|
-
const out = await relayImpl(res, lastErr.upstream, body, {
|
|
25
|
-
onFirstChunk: (d) => mark(`ttf-${lastErr.model}`),
|
|
26
|
-
onDownstreamAbort: () => evt("client-abort", { reqId: handlerCtx.reqId, model: lastErr.model, totalMs: Math.round(performance.now() - perf0), stages: [...stages] }),
|
|
27
|
-
});
|
|
28
|
-
// 收场回收(须在 relay-done 事件快照 detail 之前,review P2-6):flush 缺尾换行的末行,语料与线上字节对齐(fix-talk-capture-frame-loss D4)
|
|
29
|
-
try { captureTalkFlush(out?.detail?.talk); } catch {}
|
|
30
|
-
evt("relay-done", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-exhausted", status: out.status, ttfMs: out.ttfMs, totalMs: out.totalMs, aborted: out.aborted, interrupted: out.interrupted ?? false, detail: out.detail ?? null });
|
|
31
|
-
// 绕过 relay-pipeline 的最后一站:对话日志照常落盘(正文只进 talk/*.log,不进 events.log)
|
|
32
|
-
// 本收尾点没有 attemptStartMs 可取(绕过 relay-pipeline 直连 relay)→ 不传 attemptMs,按回退路径走:
|
|
33
|
-
// elapsed=relay 内部计时(与 relayMs 同值,spec 降级 scenario);finishReason 传 out.detail.sawFinishReason 与 3.3 同源。
|
|
34
|
-
try { recordRelayTalk({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-exhausted", body, out, finishReason: out.detail?.sawFinishReason }); } catch {}
|
|
35
|
-
// 回路语料另起一条独立 try:talk.log 是人读环形稿、capture 是机读全量语料,两者生死独立。
|
|
36
|
-
// hops 走 handlerCtx.hops(serial-trial 以 {...handlerCtx} 展开传进来,与 reqId 同源);缺值时不兜 0 ——
|
|
37
|
-
// 交给 talk-full 落 null(「hops 未知」在语料里看得见,比伪造成 0 诚实),组员流量拦截靠真值。
|
|
38
|
-
try { recordAgentLoop({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-exhausted", hops: handlerCtx?.hops, body, out, sessionKey: handlerCtx?.sessionId, clientIp: handlerCtx?.clientIp }); } catch {} // attemptMs 缺省 → 回退 out.totalMs;缺 echo 的既有形状本期不补(tasks 3.4)
|
|
39
|
-
evt("result", { reqId: handlerCtx.reqId, model: lastErr.model, status: out.status, via: "local", timing: lastErr.upstream._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, detail: out.detail ?? null });
|
|
40
|
-
evt("client-response", { requested, actual: lastErr.model, via: "local", fallback: false, status: out.status, reqId: handlerCtx.reqId });
|
|
41
|
-
// 最后一站:relay 超时/空轮留口路径都不写响应,这里没有上层,必须自己收尾
|
|
42
|
-
if (out.timedOut || out.heldOpen) {
|
|
43
|
-
terminal(res, out.detail?.exitReason === "empty-turn-hold"
|
|
44
|
-
? "EMPTY_MODEL_RESPONSE: 空轮重拉与换候选全部用尽,仍无正文"
|
|
45
|
-
: out.detail?.upstreamError ? `upstream error: ${out.detail.upstreamError}` : `stream timed out after ${out.totalMs}ms`);
|
|
46
|
-
}
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
evt("result", { reqId: handlerCtx.reqId, model, status: lastErr?.status ?? 502, via: "none", timing: null });
|
|
50
|
-
evt("client-response", { requested, actual: model, via: "none", fallback: false, status: lastErr?.status ?? 502, reqId: handlerCtx.reqId });
|
|
51
|
-
done({ via: "none", status: lastErr?.status ?? 502, error: lastErr?.message || "all auto models failed" });
|
|
52
|
-
terminal(res, lastErr?.message);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function handleExhaustedAll({ res, body, lastErr, order, requested, handlerCtx, evt, logCall, mark, perf0, stages, deps = {} }) {
|
|
57
|
-
const { relay: relayImpl = relay } = deps;
|
|
58
|
-
evt("exhausted-all", { reqId: handlerCtx.reqId, lastModel: lastErr?.model ?? requested, lastStatus: lastErr?.status ?? 502, order });
|
|
59
|
-
logCall(lastErr?.model ?? requested, lastErr?.status ?? 502);
|
|
60
|
-
if (lastErr?.upstream) {
|
|
61
|
-
evt("relay-start", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-final", isStream: Boolean(body.stream) });
|
|
62
|
-
const out = await relayImpl(res, lastErr.upstream, body, {
|
|
63
|
-
onFirstChunk: (d) => mark(`ttf-${lastErr.model}`),
|
|
64
|
-
onDownstreamAbort: () => evt("client-abort", { reqId: handlerCtx.reqId, model: lastErr.model, totalMs: Math.round(performance.now() - perf0), stages: [...stages] }),
|
|
65
|
-
});
|
|
66
|
-
// 收场回收:与 local-exhausted 同理,须在 relay-done 事件快照 detail 之前(fix-talk-capture-frame-loss D4 / review P2-6)
|
|
67
|
-
try { captureTalkFlush(out?.detail?.talk); } catch {}
|
|
68
|
-
evt("relay-done", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-final", status: out.status, ttfMs: out.ttfMs, totalMs: out.totalMs, aborted: out.aborted, interrupted: out.interrupted ?? false, detail: out.detail ?? null });
|
|
69
|
-
// 同上:全链耗尽的最终转发也要留下问答原文
|
|
70
|
-
// 同上:本收尾点没有 attemptStartMs 可取(relay 直连、无尝试起点)→ 不传 attemptMs,
|
|
71
|
-
// recordRelayTalk/recordAgentLoop 内部按回退路径走 elapsed=relay 计时(与 relayMs 同值,spec 降级 scenario);finishReason 同源传 out.detail.sawFinishReason。
|
|
72
|
-
try { recordRelayTalk({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-final", body, out, finishReason: out.detail?.sawFinishReason }); } catch {}
|
|
73
|
-
// 同上:全链耗尽的最终转发也要进语料(这条往往是「客户端只看到 502」时唯一的现场材料)
|
|
74
|
-
try { recordAgentLoop({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-final", hops: handlerCtx?.hops, body, out, sessionKey: handlerCtx?.sessionId, clientIp: handlerCtx?.clientIp }); } catch {} // attemptMs 缺省 → 回退 out.totalMs(见上行注释;缺 echo 的形状本期不补)
|
|
75
|
-
evt("result", { reqId: handlerCtx.reqId, model: lastErr.model, status: out.status, via: "local", timing: lastErr.upstream._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, detail: out.detail ?? null });
|
|
76
|
-
evt("client-response", { requested, actual: lastErr.model, via: "local", fallback: false, status: out.status, reqId: handlerCtx.reqId });
|
|
77
|
-
// 最后一站:relay 超时/空轮留口路径不写响应,这里没有上层 failover,必须自己收尾
|
|
78
|
-
if (out.timedOut || out.heldOpen) {
|
|
79
|
-
terminal(res, out.detail?.upstreamError ? `upstream error: ${out.detail.upstreamError}` : `stream timed out after ${out.totalMs}ms`);
|
|
80
|
-
}
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
evt("result", { reqId: handlerCtx.reqId, model: lastErr?.model ?? requested, status: lastErr?.status ?? 502, via: "none", timing: null });
|
|
84
|
-
evt("client-response", { requested, actual: lastErr?.model ?? requested, via: "none", fallback: false, status: lastErr?.status ?? 502, reqId: handlerCtx.reqId });
|
|
85
|
-
terminal(res, lastErr?.message);
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
1
|
+
// Note: 失败收尾四分支在 result 后补 client-response,与成功路径对账口径一致(result 条数 == client-response 条数)— 见 .agents/notes/implemented/bug-fix/2026-09-25-sdk-headers-timeout-and-failure-client-response.md
|
|
2
|
+
import { relay } from "../stream.js";
|
|
3
|
+
import { endHeldFailure } from "../stream-hold.js"; // 终局收场出口(502 JSON 或 SSE 错误帧 + [DONE])
|
|
4
|
+
import { json } from "../helpers.js";
|
|
5
|
+
import { performance } from "node:perf_hooks";
|
|
6
|
+
import { recordRelayTalk } from "../../talk-log.js"; // 环形对话日志:exhausted 收尾直连 relay,落盘点必须自己补一份
|
|
7
|
+
import { recordAgentLoop } from "../../talk-full.js"; // agent 回路全量语料(talk/full):与 talk.log 并排各调一次,见 openspec add-agent-loop-capture D1
|
|
8
|
+
import { captureTalkFlush } from "../stream-scan.js"; // 收场回收缺尾换行的末行,record* 读桶前调用(openspec fix-talk-capture-frame-loss D4)
|
|
9
|
+
|
|
10
|
+
// 最后一站的收场:relay 的「不写响应」路径(超时 / 空轮延后封口)都汇到这里,没有上层 failover 了,
|
|
11
|
+
// 必须把响应「恰好一次」地关掉。形状由 endHeldFailure 按 headers 是否已 flush 决定
|
|
12
|
+
// (未 flush → 502 JSON;已 flush 的 SSE → 错误帧 + [DONE]),这里不再自己 json() 打已 flush 的流。
|
|
13
|
+
function terminal(res, message) {
|
|
14
|
+
endHeldFailure(res, message || "all auto models failed");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function handleExhaustedLocal({ res, body, lastErr, order, handlerCtx, evt, logCall, mark, perf0, stages, done, requested, useAuto, deps = {} }) {
|
|
18
|
+
const { relay: relayImpl = relay } = deps;
|
|
19
|
+
const model = lastErr?.model ?? handlerCtx.model;
|
|
20
|
+
evt("exhausted-local", { reqId: handlerCtx.reqId, lastModel: lastErr?.model ?? model, lastStatus: lastErr?.status ?? 502, order });
|
|
21
|
+
logCall(lastErr?.model ?? model, lastErr?.status ?? 502);
|
|
22
|
+
if (lastErr?.upstream) {
|
|
23
|
+
evt("relay-start", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-exhausted", isStream: Boolean(body.stream) });
|
|
24
|
+
const out = await relayImpl(res, lastErr.upstream, body, {
|
|
25
|
+
onFirstChunk: (d) => mark(`ttf-${lastErr.model}`),
|
|
26
|
+
onDownstreamAbort: () => evt("client-abort", { reqId: handlerCtx.reqId, model: lastErr.model, totalMs: Math.round(performance.now() - perf0), stages: [...stages] }),
|
|
27
|
+
});
|
|
28
|
+
// 收场回收(须在 relay-done 事件快照 detail 之前,review P2-6):flush 缺尾换行的末行,语料与线上字节对齐(fix-talk-capture-frame-loss D4)
|
|
29
|
+
try { captureTalkFlush(out?.detail?.talk); } catch {}
|
|
30
|
+
evt("relay-done", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-exhausted", status: out.status, ttfMs: out.ttfMs, totalMs: out.totalMs, aborted: out.aborted, interrupted: out.interrupted ?? false, detail: out.detail ?? null });
|
|
31
|
+
// 绕过 relay-pipeline 的最后一站:对话日志照常落盘(正文只进 talk/*.log,不进 events.log)
|
|
32
|
+
// 本收尾点没有 attemptStartMs 可取(绕过 relay-pipeline 直连 relay)→ 不传 attemptMs,按回退路径走:
|
|
33
|
+
// elapsed=relay 内部计时(与 relayMs 同值,spec 降级 scenario);finishReason 传 out.detail.sawFinishReason 与 3.3 同源。
|
|
34
|
+
try { recordRelayTalk({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-exhausted", body, out, finishReason: out.detail?.sawFinishReason }); } catch {}
|
|
35
|
+
// 回路语料另起一条独立 try:talk.log 是人读环形稿、capture 是机读全量语料,两者生死独立。
|
|
36
|
+
// hops 走 handlerCtx.hops(serial-trial 以 {...handlerCtx} 展开传进来,与 reqId 同源);缺值时不兜 0 ——
|
|
37
|
+
// 交给 talk-full 落 null(「hops 未知」在语料里看得见,比伪造成 0 诚实),组员流量拦截靠真值。
|
|
38
|
+
try { recordAgentLoop({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-exhausted", hops: handlerCtx?.hops, body, out, sessionKey: handlerCtx?.sessionId, clientIp: handlerCtx?.clientIp }); } catch {} // attemptMs 缺省 → 回退 out.totalMs;缺 echo 的既有形状本期不补(tasks 3.4)
|
|
39
|
+
evt("result", { reqId: handlerCtx.reqId, model: lastErr.model, status: out.status, via: "local", timing: lastErr.upstream._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, detail: out.detail ?? null });
|
|
40
|
+
evt("client-response", { requested, actual: lastErr.model, via: "local", fallback: false, status: out.status, reqId: handlerCtx.reqId });
|
|
41
|
+
// 最后一站:relay 超时/空轮留口路径都不写响应,这里没有上层,必须自己收尾
|
|
42
|
+
if (out.timedOut || out.heldOpen) {
|
|
43
|
+
terminal(res, out.detail?.exitReason === "empty-turn-hold"
|
|
44
|
+
? "EMPTY_MODEL_RESPONSE: 空轮重拉与换候选全部用尽,仍无正文"
|
|
45
|
+
: out.detail?.upstreamError ? `upstream error: ${out.detail.upstreamError}` : `stream timed out after ${out.totalMs}ms`);
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
evt("result", { reqId: handlerCtx.reqId, model, status: lastErr?.status ?? 502, via: "none", timing: null });
|
|
50
|
+
evt("client-response", { requested, actual: model, via: "none", fallback: false, status: lastErr?.status ?? 502, reqId: handlerCtx.reqId });
|
|
51
|
+
done({ via: "none", status: lastErr?.status ?? 502, error: lastErr?.message || "all auto models failed" });
|
|
52
|
+
terminal(res, lastErr?.message);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function handleExhaustedAll({ res, body, lastErr, order, requested, handlerCtx, evt, logCall, mark, perf0, stages, deps = {} }) {
|
|
57
|
+
const { relay: relayImpl = relay } = deps;
|
|
58
|
+
evt("exhausted-all", { reqId: handlerCtx.reqId, lastModel: lastErr?.model ?? requested, lastStatus: lastErr?.status ?? 502, order });
|
|
59
|
+
logCall(lastErr?.model ?? requested, lastErr?.status ?? 502);
|
|
60
|
+
if (lastErr?.upstream) {
|
|
61
|
+
evt("relay-start", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-final", isStream: Boolean(body.stream) });
|
|
62
|
+
const out = await relayImpl(res, lastErr.upstream, body, {
|
|
63
|
+
onFirstChunk: (d) => mark(`ttf-${lastErr.model}`),
|
|
64
|
+
onDownstreamAbort: () => evt("client-abort", { reqId: handlerCtx.reqId, model: lastErr.model, totalMs: Math.round(performance.now() - perf0), stages: [...stages] }),
|
|
65
|
+
});
|
|
66
|
+
// 收场回收:与 local-exhausted 同理,须在 relay-done 事件快照 detail 之前(fix-talk-capture-frame-loss D4 / review P2-6)
|
|
67
|
+
try { captureTalkFlush(out?.detail?.talk); } catch {}
|
|
68
|
+
evt("relay-done", { reqId: handlerCtx.reqId, model: lastErr.model, via: "local-final", status: out.status, ttfMs: out.ttfMs, totalMs: out.totalMs, aborted: out.aborted, interrupted: out.interrupted ?? false, detail: out.detail ?? null });
|
|
69
|
+
// 同上:全链耗尽的最终转发也要留下问答原文
|
|
70
|
+
// 同上:本收尾点没有 attemptStartMs 可取(relay 直连、无尝试起点)→ 不传 attemptMs,
|
|
71
|
+
// recordRelayTalk/recordAgentLoop 内部按回退路径走 elapsed=relay 计时(与 relayMs 同值,spec 降级 scenario);finishReason 同源传 out.detail.sawFinishReason。
|
|
72
|
+
try { recordRelayTalk({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-final", body, out, finishReason: out.detail?.sawFinishReason }); } catch {}
|
|
73
|
+
// 同上:全链耗尽的最终转发也要进语料(这条往往是「客户端只看到 502」时唯一的现场材料)
|
|
74
|
+
try { recordAgentLoop({ reqId: handlerCtx.reqId, api: handlerCtx?.api, model: lastErr.model, via: "local-final", hops: handlerCtx?.hops, body, out, sessionKey: handlerCtx?.sessionId, clientIp: handlerCtx?.clientIp }); } catch {} // attemptMs 缺省 → 回退 out.totalMs(见上行注释;缺 echo 的形状本期不补)
|
|
75
|
+
evt("result", { reqId: handlerCtx.reqId, model: lastErr.model, status: out.status, via: "local", timing: lastErr.upstream._t ?? null, ttfMs: out.ttfMs, totalMs: out.totalMs, detail: out.detail ?? null });
|
|
76
|
+
evt("client-response", { requested, actual: lastErr.model, via: "local", fallback: false, status: out.status, reqId: handlerCtx.reqId });
|
|
77
|
+
// 最后一站:relay 超时/空轮留口路径不写响应,这里没有上层 failover,必须自己收尾
|
|
78
|
+
if (out.timedOut || out.heldOpen) {
|
|
79
|
+
terminal(res, out.detail?.upstreamError ? `upstream error: ${out.detail.upstreamError}` : `stream timed out after ${out.totalMs}ms`);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
evt("result", { reqId: handlerCtx.reqId, model: lastErr?.model ?? requested, status: lastErr?.status ?? 502, via: "none", timing: null });
|
|
84
|
+
evt("client-response", { requested, actual: lastErr?.model ?? requested, via: "none", fallback: false, status: lastErr?.status ?? 502, reqId: handlerCtx.reqId });
|
|
85
|
+
terminal(res, lastErr?.message);
|
|
86
|
+
return true;
|
|
87
|
+
}
|