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
package/src/routes/groups.js
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import { clientIp, json, readBody, errMsg } from "./helpers.js";
|
|
2
|
-
import { fmtShanghaiYMDHMS } from "../time.js";
|
|
3
|
-
|
|
4
|
-
export async function joinHandler({ req, res, groups, token, bans }) {
|
|
5
|
-
if (!groups) return json(res, 501, { error: "Groups service not configured" });
|
|
6
|
-
const ip = clientIp(req);
|
|
7
|
-
const banned = bans?.isBanned(ip);
|
|
8
|
-
if (banned) {
|
|
9
|
-
return json(res, 403, { error: `banned until ${fmtShanghaiYMDHMS(banned.until)}` });
|
|
10
|
-
}
|
|
11
|
-
let body;
|
|
12
|
-
try {
|
|
13
|
-
body = await readBody(req);
|
|
14
|
-
} catch {
|
|
15
|
-
return json(res, 400, { error: "Invalid JSON body" });
|
|
16
|
-
}
|
|
17
|
-
if (!body?.name) return json(res, 400, { error: "group name is required" });
|
|
18
|
-
|
|
19
|
-
const fail = (msg) => {
|
|
20
|
-
try {
|
|
21
|
-
if (bans) {
|
|
22
|
-
const b = bans.recordFailure(ip);
|
|
23
|
-
if (b) console.error(`${ip} banned (${bans.threshold} failed joins)`);
|
|
24
|
-
}
|
|
25
|
-
} catch {}
|
|
26
|
-
return json(res, 403, { error: msg });
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (!body.key) {
|
|
30
|
-
const auth = /^Bearer (.+)$/.exec(req.headers["authorization"] || "");
|
|
31
|
-
const hit = auth && groups.membersForToken(body.name, auth[1]);
|
|
32
|
-
if (!hit) return fail("invalid member token");
|
|
33
|
-
try {
|
|
34
|
-
const isBroadbandRe = String(body.url || hit.member?.url || "").startsWith("relay://") || body.kind === "broadband" || hit.member?.kind === "broadband";
|
|
35
|
-
const extra = {};
|
|
36
|
-
if (isBroadbandRe) {
|
|
37
|
-
extra.kind = "broadband";
|
|
38
|
-
extra.publicIp = ip;
|
|
39
|
-
extra.lastSeen = Date.now();
|
|
40
|
-
if (body.url) extra.url = String(body.url);
|
|
41
|
-
}
|
|
42
|
-
const refreshed = groups.upsertMember(body.name, {
|
|
43
|
-
memberName: body.memberName,
|
|
44
|
-
url: body.url || hit.member.url,
|
|
45
|
-
token: body.token || hit.member.token,
|
|
46
|
-
kind: extra.kind,
|
|
47
|
-
publicIp: extra.publicIp,
|
|
48
|
-
lastSeen: extra.lastSeen,
|
|
49
|
-
});
|
|
50
|
-
if (isBroadbandRe && refreshed) {
|
|
51
|
-
const targetId = Object.keys(refreshed).find((k) => refreshed[k].url === (body.url || hit.member.url));
|
|
52
|
-
if (targetId) {
|
|
53
|
-
refreshed[targetId].publicIp = ip;
|
|
54
|
-
refreshed[targetId].lastSeen = Date.now();
|
|
55
|
-
refreshed[targetId].kind = "broadband";
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return json(res, 200, { object: "group", name: body.name, members: refreshed });
|
|
59
|
-
} catch (err) {
|
|
60
|
-
return json(res, 400, { error: errMsg(err) });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const youPort = Number(body.myPort);
|
|
66
|
-
const youUrl = Number.isInteger(youPort) && youPort > 0 ? `http://${ip}:${youPort}` : "";
|
|
67
|
-
let memberUrl = String(body.url || youUrl);
|
|
68
|
-
if (!memberUrl) throw new Error("member url is required");
|
|
69
|
-
const isBroadband = String(memberUrl).startsWith("relay://") || body.kind === "broadband";
|
|
70
|
-
if (isBroadband) {
|
|
71
|
-
memberUrl = String(body.url || memberUrl);
|
|
72
|
-
}
|
|
73
|
-
const members = groups.addMember(body.name, {
|
|
74
|
-
key: body.key,
|
|
75
|
-
memberName: body.memberName,
|
|
76
|
-
url: memberUrl,
|
|
77
|
-
token: body.token,
|
|
78
|
-
kind: isBroadband ? "broadband" : "static",
|
|
79
|
-
publicIp: isBroadband ? ip : undefined,
|
|
80
|
-
lastSeen: isBroadband ? Date.now() : undefined,
|
|
81
|
-
});
|
|
82
|
-
if (bans) bans.clear(ip);
|
|
83
|
-
if (!members.leader) {
|
|
84
|
-
const leaderUrl = String(body.leaderUrl || "").replace(/\/+$/, "");
|
|
85
|
-
if (leaderUrl) {
|
|
86
|
-
groups.upsertMember(body.name, { memberName: "leader", url: leaderUrl, token });
|
|
87
|
-
Object.assign(members, { leader: { url: leaderUrl, token } });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
json(res, 200, { object: "group", name: body.name, members, you: { url: memberUrl } });
|
|
91
|
-
} catch (err) {
|
|
92
|
-
return fail(errMsg(err));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function leaveHandler({ req, res, groups }) {
|
|
97
|
-
if (!groups) return json(res, 501, { error: "Groups service not configured" });
|
|
98
|
-
let body;
|
|
99
|
-
try {
|
|
100
|
-
body = await readBody(req);
|
|
101
|
-
} catch {
|
|
102
|
-
return json(res, 400, { error: "Invalid JSON body" });
|
|
103
|
-
}
|
|
104
|
-
if (!body?.name) return json(res, 400, { error: "group name is required" });
|
|
105
|
-
const auth = /^Bearer (.+)$/.exec(req.headers["authorization"] || "");
|
|
106
|
-
if (!auth) return json(res, 401, { error: "bearer token required" });
|
|
107
|
-
const group = groups.list()[body.name];
|
|
108
|
-
if (!group) return json(res, 404, { error: `group "${body.name}" not found` });
|
|
109
|
-
const hit = groups.membersForToken(body.name, auth[1]);
|
|
110
|
-
if (!hit) return json(res, 403, { error: "invalid member token" });
|
|
111
|
-
try {
|
|
112
|
-
const removed = groups.removeMember(body.name, { url: hit.member.url });
|
|
113
|
-
return json(res, 200, {
|
|
114
|
-
object: "group",
|
|
115
|
-
name: body.name,
|
|
116
|
-
removed: removed?.removed ?? null,
|
|
117
|
-
members: groups.list()[body.name]?.members ?? {},
|
|
118
|
-
});
|
|
119
|
-
} catch (err) {
|
|
120
|
-
return json(res, 400, { error: errMsg(err) });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
1
|
+
import { clientIp, json, readBody, errMsg } from "./helpers.js";
|
|
2
|
+
import { fmtShanghaiYMDHMS } from "../time.js";
|
|
3
|
+
|
|
4
|
+
export async function joinHandler({ req, res, groups, token, bans }) {
|
|
5
|
+
if (!groups) return json(res, 501, { error: "Groups service not configured" });
|
|
6
|
+
const ip = clientIp(req);
|
|
7
|
+
const banned = bans?.isBanned(ip);
|
|
8
|
+
if (banned) {
|
|
9
|
+
return json(res, 403, { error: `banned until ${fmtShanghaiYMDHMS(banned.until)}` });
|
|
10
|
+
}
|
|
11
|
+
let body;
|
|
12
|
+
try {
|
|
13
|
+
body = await readBody(req);
|
|
14
|
+
} catch {
|
|
15
|
+
return json(res, 400, { error: "Invalid JSON body" });
|
|
16
|
+
}
|
|
17
|
+
if (!body?.name) return json(res, 400, { error: "group name is required" });
|
|
18
|
+
|
|
19
|
+
const fail = (msg) => {
|
|
20
|
+
try {
|
|
21
|
+
if (bans) {
|
|
22
|
+
const b = bans.recordFailure(ip);
|
|
23
|
+
if (b) console.error(`${ip} banned (${bans.threshold} failed joins)`);
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
return json(res, 403, { error: msg });
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (!body.key) {
|
|
30
|
+
const auth = /^Bearer (.+)$/.exec(req.headers["authorization"] || "");
|
|
31
|
+
const hit = auth && groups.membersForToken(body.name, auth[1]);
|
|
32
|
+
if (!hit) return fail("invalid member token");
|
|
33
|
+
try {
|
|
34
|
+
const isBroadbandRe = String(body.url || hit.member?.url || "").startsWith("relay://") || body.kind === "broadband" || hit.member?.kind === "broadband";
|
|
35
|
+
const extra = {};
|
|
36
|
+
if (isBroadbandRe) {
|
|
37
|
+
extra.kind = "broadband";
|
|
38
|
+
extra.publicIp = ip;
|
|
39
|
+
extra.lastSeen = Date.now();
|
|
40
|
+
if (body.url) extra.url = String(body.url);
|
|
41
|
+
}
|
|
42
|
+
const refreshed = groups.upsertMember(body.name, {
|
|
43
|
+
memberName: body.memberName,
|
|
44
|
+
url: body.url || hit.member.url,
|
|
45
|
+
token: body.token || hit.member.token,
|
|
46
|
+
kind: extra.kind,
|
|
47
|
+
publicIp: extra.publicIp,
|
|
48
|
+
lastSeen: extra.lastSeen,
|
|
49
|
+
});
|
|
50
|
+
if (isBroadbandRe && refreshed) {
|
|
51
|
+
const targetId = Object.keys(refreshed).find((k) => refreshed[k].url === (body.url || hit.member.url));
|
|
52
|
+
if (targetId) {
|
|
53
|
+
refreshed[targetId].publicIp = ip;
|
|
54
|
+
refreshed[targetId].lastSeen = Date.now();
|
|
55
|
+
refreshed[targetId].kind = "broadband";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return json(res, 200, { object: "group", name: body.name, members: refreshed });
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return json(res, 400, { error: errMsg(err) });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const youPort = Number(body.myPort);
|
|
66
|
+
const youUrl = Number.isInteger(youPort) && youPort > 0 ? `http://${ip}:${youPort}` : "";
|
|
67
|
+
let memberUrl = String(body.url || youUrl);
|
|
68
|
+
if (!memberUrl) throw new Error("member url is required");
|
|
69
|
+
const isBroadband = String(memberUrl).startsWith("relay://") || body.kind === "broadband";
|
|
70
|
+
if (isBroadband) {
|
|
71
|
+
memberUrl = String(body.url || memberUrl);
|
|
72
|
+
}
|
|
73
|
+
const members = groups.addMember(body.name, {
|
|
74
|
+
key: body.key,
|
|
75
|
+
memberName: body.memberName,
|
|
76
|
+
url: memberUrl,
|
|
77
|
+
token: body.token,
|
|
78
|
+
kind: isBroadband ? "broadband" : "static",
|
|
79
|
+
publicIp: isBroadband ? ip : undefined,
|
|
80
|
+
lastSeen: isBroadband ? Date.now() : undefined,
|
|
81
|
+
});
|
|
82
|
+
if (bans) bans.clear(ip);
|
|
83
|
+
if (!members.leader) {
|
|
84
|
+
const leaderUrl = String(body.leaderUrl || "").replace(/\/+$/, "");
|
|
85
|
+
if (leaderUrl) {
|
|
86
|
+
groups.upsertMember(body.name, { memberName: "leader", url: leaderUrl, token });
|
|
87
|
+
Object.assign(members, { leader: { url: leaderUrl, token } });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
json(res, 200, { object: "group", name: body.name, members, you: { url: memberUrl } });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return fail(errMsg(err));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function leaveHandler({ req, res, groups }) {
|
|
97
|
+
if (!groups) return json(res, 501, { error: "Groups service not configured" });
|
|
98
|
+
let body;
|
|
99
|
+
try {
|
|
100
|
+
body = await readBody(req);
|
|
101
|
+
} catch {
|
|
102
|
+
return json(res, 400, { error: "Invalid JSON body" });
|
|
103
|
+
}
|
|
104
|
+
if (!body?.name) return json(res, 400, { error: "group name is required" });
|
|
105
|
+
const auth = /^Bearer (.+)$/.exec(req.headers["authorization"] || "");
|
|
106
|
+
if (!auth) return json(res, 401, { error: "bearer token required" });
|
|
107
|
+
const group = groups.list()[body.name];
|
|
108
|
+
if (!group) return json(res, 404, { error: `group "${body.name}" not found` });
|
|
109
|
+
const hit = groups.membersForToken(body.name, auth[1]);
|
|
110
|
+
if (!hit) return json(res, 403, { error: "invalid member token" });
|
|
111
|
+
try {
|
|
112
|
+
const removed = groups.removeMember(body.name, { url: hit.member.url });
|
|
113
|
+
return json(res, 200, {
|
|
114
|
+
object: "group",
|
|
115
|
+
name: body.name,
|
|
116
|
+
removed: removed?.removed ?? null,
|
|
117
|
+
members: groups.list()[body.name]?.members ?? {},
|
|
118
|
+
});
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return json(res, 400, { error: errMsg(err) });
|
|
121
|
+
}
|
|
122
|
+
}
|
package/src/routes/helpers.js
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import { timingSafeEqual, createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
export const errMsg = (err) => String(err?.message || err);
|
|
4
|
-
|
|
5
|
-
export function clientIp(req) {
|
|
6
|
-
const fwd = req.headers["x-forwarded-for"];
|
|
7
|
-
const head = typeof fwd === "string" ? fwd.split(",")[0].trim() : "";
|
|
8
|
-
const raw = String(head || req.socket.remoteAddress || "");
|
|
9
|
-
return raw.replace(/^::ffff:/, "").replace(/^::1$/, "127.0.0.1") || null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function authorized(req, token) {
|
|
13
|
-
const header = req.headers["authorization"] || "";
|
|
14
|
-
const match = /^Bearer (.+)$/.exec(header);
|
|
15
|
-
if (!match) return false;
|
|
16
|
-
const digests = (s) => createHash("sha256").update(s).digest();
|
|
17
|
-
return timingSafeEqual(digests(match[1]), digests(token));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function json(res, status, body) {
|
|
21
|
-
// 幂等守卫:已收场的响应必须 no-op(空轮 hold 后多处共用收场出口,二次 end 会抛)
|
|
22
|
-
if (!res || res.writableEnded) return;
|
|
23
|
-
if (!res.headersSent) {
|
|
24
|
-
res.statusCode = status;
|
|
25
|
-
try { res.setHeader("Content-Type", "application/json"); } catch { /* ignore */ }
|
|
26
|
-
try { res.end(JSON.stringify(body)); } catch { /* ignore */ }
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// headers 已 flush(failover 前候选写过 SSE 注释帧、或空轮 hold 期间发过 keepalive):
|
|
30
|
-
// 设 statusCode 会抛 ERR_HTTP_HEADERS_SENT,写 JSON 体更会把 application/json 混进 text/event-stream
|
|
31
|
-
// (客户端只会当噪声丢掉,等于悄悄吞掉错误)。SSE 一律改用 in-band 错误帧 + [DONE] 收场。
|
|
32
|
-
const ct = typeof res.getHeader === "function" ? String(res.getHeader("content-type") || "") : "";
|
|
33
|
-
if (ct.includes("text/event-stream")) {
|
|
34
|
-
try {
|
|
35
|
-
const msg = body && typeof body === "object" ? String(body.error ?? body.message ?? JSON.stringify(body)) : String(body ?? "error");
|
|
36
|
-
res.write(`data: ${JSON.stringify({ error: { message: msg, type: "mslxdff_error" } })}\n\n`);
|
|
37
|
-
res.write("data: [DONE]\n\n");
|
|
38
|
-
} catch { /* 下游已断 */ }
|
|
39
|
-
}
|
|
40
|
-
try { res.end(); } catch { /* ignore */ }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function notFound(res) {
|
|
44
|
-
return json(res, 404, { error: "Not Found" });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function readBody(req) {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
let data = "";
|
|
50
|
-
req.on("data", (c) => (data += c));
|
|
51
|
-
req.on("end", () => {
|
|
52
|
-
try {
|
|
53
|
-
resolve(data ? JSON.parse(data) : {});
|
|
54
|
-
} catch (err) {
|
|
55
|
-
reject(err);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
req.on("error", reject);
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function parseHops(header) {
|
|
63
|
-
const n = Number(header);
|
|
64
|
-
return Number.isInteger(n) && n >= 0 ? n : 0;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const PROMPT_MAX_LEN = 160;
|
|
68
|
-
|
|
69
|
-
export function summarizePrompt(body) {
|
|
70
|
-
const msgs = body?.messages;
|
|
71
|
-
if (!Array.isArray(msgs) || !msgs.length) return "";
|
|
72
|
-
const msg = msgs[msgs.length - 1];
|
|
73
|
-
const c = msg?.content;
|
|
74
|
-
let text = "";
|
|
75
|
-
if (typeof c === "string") text = c;
|
|
76
|
-
else if (Array.isArray(c)) {
|
|
77
|
-
text = c
|
|
78
|
-
.map((p) => (typeof p === "string" ? p : p && typeof p.text === "string" ? p.text : ""))
|
|
79
|
-
.join(" ");
|
|
80
|
-
}
|
|
81
|
-
text = String(text || "").replace(/\s+/g, " ").trim();
|
|
82
|
-
return text.length > PROMPT_MAX_LEN ? text.slice(0, PROMPT_MAX_LEN) + "…" : text;
|
|
83
|
-
}
|
|
1
|
+
import { timingSafeEqual, createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const errMsg = (err) => String(err?.message || err);
|
|
4
|
+
|
|
5
|
+
export function clientIp(req) {
|
|
6
|
+
const fwd = req.headers["x-forwarded-for"];
|
|
7
|
+
const head = typeof fwd === "string" ? fwd.split(",")[0].trim() : "";
|
|
8
|
+
const raw = String(head || req.socket.remoteAddress || "");
|
|
9
|
+
return raw.replace(/^::ffff:/, "").replace(/^::1$/, "127.0.0.1") || null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function authorized(req, token) {
|
|
13
|
+
const header = req.headers["authorization"] || "";
|
|
14
|
+
const match = /^Bearer (.+)$/.exec(header);
|
|
15
|
+
if (!match) return false;
|
|
16
|
+
const digests = (s) => createHash("sha256").update(s).digest();
|
|
17
|
+
return timingSafeEqual(digests(match[1]), digests(token));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function json(res, status, body) {
|
|
21
|
+
// 幂等守卫:已收场的响应必须 no-op(空轮 hold 后多处共用收场出口,二次 end 会抛)
|
|
22
|
+
if (!res || res.writableEnded) return;
|
|
23
|
+
if (!res.headersSent) {
|
|
24
|
+
res.statusCode = status;
|
|
25
|
+
try { res.setHeader("Content-Type", "application/json"); } catch { /* ignore */ }
|
|
26
|
+
try { res.end(JSON.stringify(body)); } catch { /* ignore */ }
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// headers 已 flush(failover 前候选写过 SSE 注释帧、或空轮 hold 期间发过 keepalive):
|
|
30
|
+
// 设 statusCode 会抛 ERR_HTTP_HEADERS_SENT,写 JSON 体更会把 application/json 混进 text/event-stream
|
|
31
|
+
// (客户端只会当噪声丢掉,等于悄悄吞掉错误)。SSE 一律改用 in-band 错误帧 + [DONE] 收场。
|
|
32
|
+
const ct = typeof res.getHeader === "function" ? String(res.getHeader("content-type") || "") : "";
|
|
33
|
+
if (ct.includes("text/event-stream")) {
|
|
34
|
+
try {
|
|
35
|
+
const msg = body && typeof body === "object" ? String(body.error ?? body.message ?? JSON.stringify(body)) : String(body ?? "error");
|
|
36
|
+
res.write(`data: ${JSON.stringify({ error: { message: msg, type: "mslxdff_error" } })}\n\n`);
|
|
37
|
+
res.write("data: [DONE]\n\n");
|
|
38
|
+
} catch { /* 下游已断 */ }
|
|
39
|
+
}
|
|
40
|
+
try { res.end(); } catch { /* ignore */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function notFound(res) {
|
|
44
|
+
return json(res, 404, { error: "Not Found" });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readBody(req) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
let data = "";
|
|
50
|
+
req.on("data", (c) => (data += c));
|
|
51
|
+
req.on("end", () => {
|
|
52
|
+
try {
|
|
53
|
+
resolve(data ? JSON.parse(data) : {});
|
|
54
|
+
} catch (err) {
|
|
55
|
+
reject(err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
req.on("error", reject);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseHops(header) {
|
|
63
|
+
const n = Number(header);
|
|
64
|
+
return Number.isInteger(n) && n >= 0 ? n : 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const PROMPT_MAX_LEN = 160;
|
|
68
|
+
|
|
69
|
+
export function summarizePrompt(body) {
|
|
70
|
+
const msgs = body?.messages;
|
|
71
|
+
if (!Array.isArray(msgs) || !msgs.length) return "";
|
|
72
|
+
const msg = msgs[msgs.length - 1];
|
|
73
|
+
const c = msg?.content;
|
|
74
|
+
let text = "";
|
|
75
|
+
if (typeof c === "string") text = c;
|
|
76
|
+
else if (Array.isArray(c)) {
|
|
77
|
+
text = c
|
|
78
|
+
.map((p) => (typeof p === "string" ? p : p && typeof p.text === "string" ? p.text : ""))
|
|
79
|
+
.join(" ");
|
|
80
|
+
}
|
|
81
|
+
text = String(text || "").replace(/\s+/g, " ").trim();
|
|
82
|
+
return text.length > PROMPT_MAX_LEN ? text.slice(0, PROMPT_MAX_LEN) + "…" : text;
|
|
83
|
+
}
|