mslxdff 0.1.155 → 0.1.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mslxdff.js +4 -4
- package/docs/ARCHITECTURE.md +426 -0
- package/docs/FEATURE_TREE.md +164 -0
- package/docs/MOBILE.md +82 -0
- package/docs/adr/0001-reasoning-content-injection.md +14 -0
- package/docs/adr/0002-models-free-filter.md +12 -0
- package/docs/adr/0003-zero-state-no-auth.md +10 -0
- package/docs/adr/0004-bearer-token.md +18 -0
- package/docs/adr/0005-peer-mesh.md +53 -0
- package/docs/adr/0006-broadband-member.md +103 -0
- package/docs/adr/0007-multi-provider-prefix.md +25 -0
- package/docs/adr/0008-share-keys-to-peers.md +52 -0
- package/docs/adr/0009-chat-repl.md +37 -0
- package/docs/adr/0010-allowlist.md +36 -0
- package/docs/adr/0011-broadband-stream.md +30 -0
- package/docs/adr/0012-responses-endpoint-codex-sync.md +48 -0
- package/docs/adr/0013-node16-compat.md +41 -0
- package/docs/adr/0014-deepseek-provider.md +52 -0
- package/docs/adr/0015-upstream-probe-routing.md +50 -0
- package/docs/adr/0016-model-capabilities.md +27 -0
- package/docs/adr/0017-ai-sdk-upstream-engine.md +59 -0
- package/docs/adr/0018-zen-client-identity.md +52 -0
- package/docs/adr/0019-share-keys-always-lend.md +59 -0
- package/docs/adr/0020-zen-free-lane-agent-shape.md +52 -0
- package/docs/adr/0021-usage-report-jsonl.md +47 -0
- package/docs/adr/0022-models-capability-merge.md +61 -0
- package/docs/adr/0023-key-provider-default-direct.md +58 -0
- package/docs/adr/0024-node18-baseline.md +63 -0
- package/docs/adr/0025-workbuddy-authdir-follows-state.md +71 -0
- package/docs/adr/0026-cline-provider-id-unify.md +67 -0
- package/docs/adr/0027-codearts-provider.md +48 -0
- package/docs/adr/0028-traework-provider.md +34 -0
- package/docs/adr/0029-qoder-native-provider.md +60 -0
- package/docs/adr/0030-models-list-scoped-by-picks.md +53 -0
- package/docs/adr/0031-qoder-true-streaming.md +50 -0
- package/docs/adr/0032-generic-responses-channel.md +72 -0
- package/docs/adr/0033-cline-allowlist-auto-sync.md +75 -0
- package/docs/adr/0034-request-level-human-readable-observability.md +60 -0
- package/docs/adr/0035-sdk-channel-headers-timeout.md +49 -0
- package/docs/adr/0036-qoder-per-request-sticky-account.md +82 -0
- package/docs/adr/0037-qwenwork-independent-provider.md +82 -0
- package/docs/adr/0038-zcode-provider.md +140 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +30 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/cli_help.md +1391 -0
- package/docs/cli_help_mini.md +133 -0
- package/docs/plans/bench-via-latency-2026-09-01.md +215 -0
- package/docs/plugins.md +187 -0
- package/package.json +1 -1
- package/src/auto.js +254 -254
- package/src/bench/cline-bench.js +42 -42
- package/src/bench/probe.js +70 -70
- package/src/bench/report.js +162 -162
- package/src/bench/runner.js +77 -77
- package/src/bench/via-probe.js +124 -124
- package/src/bench/via-routes.js +87 -87
- package/src/bench/workbuddy-bench.js +54 -54
- package/src/chat/engine.js +160 -160
- package/src/chat/gateway.js +163 -163
- package/src/chat/orchestrator.js +234 -234
- package/src/chat/prompt.js +70 -70
- package/src/chat/repl.js +88 -88
- package/src/chat/terminal.js +135 -135
- package/src/chat/tools.js +306 -306
- package/src/chat-pipeline/index.js +123 -123
- package/src/chat-pipeline/policy.js +76 -76
- package/src/chat-pipeline/serial-trial.js +210 -210
- package/src/cli/commands/group.js +249 -249
- package/src/cli/commands/model/list-providers.js +4 -3
- package/src/cli/commands/model/list-render.js +7 -9
- package/src/cli/commands/model/list-sort.js +52 -0
- package/src/cli/commands/model/list-tty.js +76 -0
- package/src/cli/commands/model/list.js +5 -62
- package/src/cli/commands/model/picks.js +50 -50
- package/src/cli/commands/provider/bench-via.js +247 -247
- package/src/cli/commands/provider/bench.js +141 -141
- package/src/cli/commands/provider/index.js +124 -116
- package/src/cli/commands/provider/models.js +139 -128
- package/src/cli/commands/provider/qwenwork-login.js +119 -0
- package/src/cli/commands/provider/zcode-login.js +77 -0
- package/src/cli/commands/provider/zcode-quota.js +55 -0
- package/src/cli/commands/sync.js +232 -232
- package/src/cli/provider-row.js +2 -2
- package/src/cli/status.js +279 -279
- package/src/daemon.js +96 -96
- package/src/model-capabilities/enrich.js +86 -86
- package/src/model-capabilities/index.js +183 -183
- package/src/model-capabilities/parse.js +70 -70
- package/src/model-trace.js +1 -0
- package/src/models.js +225 -225
- package/src/providers/classify.js +1 -1
- package/src/providers/cline/auth.js +228 -228
- package/src/providers/cline/chat.js +307 -307
- package/src/providers/cline.js +2 -2
- package/src/providers/keyring.js +60 -56
- package/src/providers/qoder/chat.js +183 -100
- package/src/providers/qoder/index.js +230 -184
- package/src/providers/qoder/sse.js +103 -46
- package/src/providers/qoder/sticky.js +1 -0
- package/src/providers/qoder/stream.js +32 -9
- package/src/providers/qwenwork/account-store.js +133 -0
- package/src/providers/qwenwork/constants.js +67 -0
- package/src/providers/qwenwork/cosy.js +120 -0
- package/src/providers/qwenwork/crypto.js +218 -0
- package/src/providers/qwenwork/http.js +20 -0
- package/src/providers/qwenwork/index.js +327 -0
- package/src/providers/qwenwork/payload.js +142 -0
- package/src/providers/qwenwork/rsa.js +54 -0
- package/src/providers/qwenwork/sse.js +268 -0
- package/src/providers/qwenwork/stream.js +130 -0
- package/src/providers/qwenwork/upstream.js +120 -0
- package/src/providers/qwenwork.js +1 -0
- package/src/providers/registry.js +66 -56
- package/src/providers/share-keys.js +2 -2
- package/src/providers/workbuddy/chat.js +248 -248
- package/src/providers/workbuddy/reshape.js +152 -152
- package/src/providers/workbuddy.js +2 -2
- package/src/providers/zcode/account-store.js +129 -0
- package/src/providers/zcode/auth.js +28 -0
- package/src/providers/zcode/chat.js +171 -0
- package/src/providers/zcode/const.js +54 -0
- package/src/providers/zcode/headers.js +55 -0
- package/src/providers/zcode/index.js +124 -0
- package/src/providers/zcode/models.js +65 -0
- package/src/providers/zcode/oauth.js +120 -0
- package/src/providers/zcode/quota.js +176 -0
- package/src/providers/zcode/sse.js +179 -0
- package/src/reasoning.js +32 -32
- package/src/routes/chat/gateway.js +46 -46
- package/src/routes/chat/relay-pipeline.js +250 -246
- package/src/routes/chat/via-route-handler.js +144 -144
- package/src/routes/hedge.js +255 -255
- package/src/routes/models-route.js +167 -167
- package/src/routes/peers.js +273 -273
- package/src/routes/stream.js +438 -399
- package/src/runtime/bootstrap.js +45 -45
- package/src/runtime/provider-gate.js +33 -30
- package/src/runtime/providers-setup.js +165 -165
- package/src/server.js +64 -64
- package/src/state/schemas/allowlist.js +92 -92
- package/src/sync-opencode.js +280 -280
- package/src/transport/index.js +244 -244
- package/src/transport/pool.js +56 -56
- package/src/transport/retry.js +24 -24
- package/src/transport/sse.js +93 -93
- package/src/upstream-probe/display.js +52 -52
- package/src/upstream-probe/probe.js +49 -49
- package/src/upstream-probe/rotate.js +110 -110
- package/src/upstream-probe/start.js +45 -45
- package/src/upstream.js +289 -289
|
@@ -1,128 +1,139 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { defaultStateFile } from "../../../state.js";
|
|
3
|
-
import { logDir } from "../../../logs.js";
|
|
4
|
-
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
5
|
-
|
|
6
|
-
export async function handleProviderModels(id, sub, args, rest) {
|
|
7
|
-
if (!(sub === "models" || sub === "show-models" || sub === "list-models" || sub === "ls")) return false;
|
|
8
|
-
// 供应商 id 归一(clinebot/cline-bot → cline):老脚本 `-provider clinebot models` 仍走 cline 直查
|
|
9
|
-
id = normalizeProviderId(id);
|
|
10
|
-
const { loadProviderConfig, loadProviderKeys, isModelAllowed } = await import("../../../state.js");
|
|
11
|
-
const wantsJson = args.includes("--json") || args.includes("-json");
|
|
12
|
-
const cfg = loadProviderConfig(id);
|
|
13
|
-
if (id === "opencode" || id === "oc") {
|
|
14
|
-
try {
|
|
15
|
-
const cacheFile = join(logDir(), "models.json");
|
|
16
|
-
const { readFileSync } = await import("node:fs");
|
|
17
|
-
const raw = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
18
|
-
const ids = (raw.data || []).map((m) => m.id).filter((x) => !String(x).includes("/"));
|
|
19
|
-
if (wantsJson) {
|
|
20
|
-
console.log(JSON.stringify({ object: "list", data: ids.map((id) => ({ id, object: "model" })) }, null, 2));
|
|
21
|
-
} else {
|
|
22
|
-
console.log(`opencode models (${ids.length}):`);
|
|
23
|
-
for (const mid of ids) console.log(` ${mid}`);
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {
|
|
26
|
-
console.error(`could not read models cache: ${String(e?.message || e)}`);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const { createGenericProvider } = await import("../../../providers/generic.js");
|
|
33
|
-
const { createWorkbuddyProvider } = await import("../../../providers/workbuddy.js");
|
|
34
|
-
const baseUrl = cfg?.baseUrl || (id === "workbuddy" ? "https://copilot.tencent.com" : "");
|
|
35
|
-
const keys = loadProviderKeys(id);
|
|
36
|
-
const auths = cfg?.auths || [];
|
|
37
|
-
let provider;
|
|
38
|
-
if (id === "workbuddy") {
|
|
39
|
-
provider = createWorkbuddyProvider({ baseUrl, apiKeys: keys, auths, file: defaultStateFile() });
|
|
40
|
-
} else if (id === "cline") {
|
|
41
|
-
// 与聚合目录同源:recommended-models 的 free(generic 的 /api/v1/models 是全量目录,含大量不可用条目)
|
|
42
|
-
const { createClineProvider } = await import("../../../providers/cline.js");
|
|
43
|
-
provider = createClineProvider({ id, baseUrl: baseUrl || undefined, apiKeys: keys, file: defaultStateFile() });
|
|
44
|
-
} else if (id === "codearts") {
|
|
45
|
-
const { createCodeartsProvider } = await import("../../../providers/codearts.js");
|
|
46
|
-
provider = createCodeartsProvider({ baseUrl: baseUrl || undefined, apiKeys: keys, file: defaultStateFile() });
|
|
47
|
-
} else if (id === "traework") {
|
|
48
|
-
const { createTraeworkProvider } = await import("../../../providers/traework.js");
|
|
49
|
-
provider = createTraeworkProvider({ baseUrl: baseUrl || undefined, apiKeys: keys, auths, file: defaultStateFile() });
|
|
50
|
-
} else if (id === "qoder") {
|
|
51
|
-
const { createQoderProvider } = await import("../../../providers/qoder/index.js");
|
|
52
|
-
provider = createQoderProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
53
|
-
} else {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
provider =
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
return
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { defaultStateFile } from "../../../state.js";
|
|
3
|
+
import { logDir } from "../../../logs.js";
|
|
4
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
5
|
+
|
|
6
|
+
export async function handleProviderModels(id, sub, args, rest) {
|
|
7
|
+
if (!(sub === "models" || sub === "show-models" || sub === "list-models" || sub === "ls")) return false;
|
|
8
|
+
// 供应商 id 归一(clinebot/cline-bot → cline):老脚本 `-provider clinebot models` 仍走 cline 直查
|
|
9
|
+
id = normalizeProviderId(id);
|
|
10
|
+
const { loadProviderConfig, loadProviderKeys, isModelAllowed } = await import("../../../state.js");
|
|
11
|
+
const wantsJson = args.includes("--json") || args.includes("-json");
|
|
12
|
+
const cfg = loadProviderConfig(id);
|
|
13
|
+
if (id === "opencode" || id === "oc") {
|
|
14
|
+
try {
|
|
15
|
+
const cacheFile = join(logDir(), "models.json");
|
|
16
|
+
const { readFileSync } = await import("node:fs");
|
|
17
|
+
const raw = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
18
|
+
const ids = (raw.data || []).map((m) => m.id).filter((x) => !String(x).includes("/"));
|
|
19
|
+
if (wantsJson) {
|
|
20
|
+
console.log(JSON.stringify({ object: "list", data: ids.map((id) => ({ id, object: "model" })) }, null, 2));
|
|
21
|
+
} else {
|
|
22
|
+
console.log(`opencode models (${ids.length}):`);
|
|
23
|
+
for (const mid of ids) console.log(` ${mid}`);
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error(`could not read models cache: ${String(e?.message || e)}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const { createGenericProvider } = await import("../../../providers/generic.js");
|
|
33
|
+
const { createWorkbuddyProvider } = await import("../../../providers/workbuddy.js");
|
|
34
|
+
const baseUrl = cfg?.baseUrl || (id === "workbuddy" ? "https://copilot.tencent.com" : "");
|
|
35
|
+
const keys = loadProviderKeys(id);
|
|
36
|
+
const auths = cfg?.auths || [];
|
|
37
|
+
let provider;
|
|
38
|
+
if (id === "workbuddy") {
|
|
39
|
+
provider = createWorkbuddyProvider({ baseUrl, apiKeys: keys, auths, file: defaultStateFile() });
|
|
40
|
+
} else if (id === "cline") {
|
|
41
|
+
// 与聚合目录同源:recommended-models 的 free(generic 的 /api/v1/models 是全量目录,含大量不可用条目)
|
|
42
|
+
const { createClineProvider } = await import("../../../providers/cline.js");
|
|
43
|
+
provider = createClineProvider({ id, baseUrl: baseUrl || undefined, apiKeys: keys, file: defaultStateFile() });
|
|
44
|
+
} else if (id === "codearts") {
|
|
45
|
+
const { createCodeartsProvider } = await import("../../../providers/codearts.js");
|
|
46
|
+
provider = createCodeartsProvider({ baseUrl: baseUrl || undefined, apiKeys: keys, file: defaultStateFile() });
|
|
47
|
+
} else if (id === "traework") {
|
|
48
|
+
const { createTraeworkProvider } = await import("../../../providers/traework.js");
|
|
49
|
+
provider = createTraeworkProvider({ baseUrl: baseUrl || undefined, apiKeys: keys, auths, file: defaultStateFile() });
|
|
50
|
+
} else if (id === "qoder") {
|
|
51
|
+
const { createQoderProvider } = await import("../../../providers/qoder/index.js");
|
|
52
|
+
provider = createQoderProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
53
|
+
} else if (id === "qwenwork") {
|
|
54
|
+
const { createQwenworkProvider } = await import("../../../providers/qwenwork/index.js");
|
|
55
|
+
provider = createQwenworkProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
56
|
+
} else if (id === "zcode") {
|
|
57
|
+
const { createZcodeProvider } = await import("../../../providers/zcode/index.js");
|
|
58
|
+
provider = createZcodeProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
59
|
+
} else {
|
|
60
|
+
if (!baseUrl) {
|
|
61
|
+
console.error(`provider ${id}: missing baseUrl — set via: mslxdff -provider ${id} set-url <baseUrl>`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
provider = createGenericProvider({ id, baseUrl, apiKeys: keys, file: defaultStateFile() });
|
|
65
|
+
}
|
|
66
|
+
const all = await provider.listModels();
|
|
67
|
+
const markAllowed = (mid) => {
|
|
68
|
+
const raw = String(mid || "").includes("/") ? String(mid).split("/").slice(1).join("/") : String(mid);
|
|
69
|
+
const checkRaw = mid.startsWith(`${id}/`) ? mid.slice(id.length + 1) : raw;
|
|
70
|
+
return isModelAllowed(id, checkRaw);
|
|
71
|
+
};
|
|
72
|
+
if (wantsJson) {
|
|
73
|
+
const filtered = all.filter((m) => markAllowed(m.id));
|
|
74
|
+
console.log(JSON.stringify({ object: "list", data: filtered }, null, 2));
|
|
75
|
+
} else {
|
|
76
|
+
const allowedCount = all.filter((m) => markAllowed(m.id)).length;
|
|
77
|
+
console.log(`${id} models (${all.length} total, ${allowedCount} ✓ allowed${allowedCount !== all.length ? `, ${all.length - allowedCount} x blocked by allowlist` : ""}):`);
|
|
78
|
+
const fmtPrice = (m) => {
|
|
79
|
+
const c = String(m.credits || "").trim();
|
|
80
|
+
if (c) {
|
|
81
|
+
const m0 = c.match(/x\s*([\d.]+)/i);
|
|
82
|
+
if (m0) return `x${m0[1]}`;
|
|
83
|
+
return c.replace(/\s*credits\s*/gi, "").trim().replace(/\s+/g, " ");
|
|
84
|
+
}
|
|
85
|
+
if (m.pricing && typeof m.pricing === "object") {
|
|
86
|
+
const p = m.pricing.prompt ?? m.pricing.input ?? m.pricing.completion ?? "";
|
|
87
|
+
if (p) return String(p);
|
|
88
|
+
}
|
|
89
|
+
if (m.price_factor != null && String(m.price_factor).trim() !== "") return `x${m.price_factor}`;
|
|
90
|
+
if (m.price != null && String(m.price).trim()) return String(m.price).trim();
|
|
91
|
+
if (String(m.id).endsWith("/auto")) return "浮动";
|
|
92
|
+
return "—";
|
|
93
|
+
};
|
|
94
|
+
const fmtBadge = (m) => {
|
|
95
|
+
const tags = Array.isArray(m.tags) ? m.tags : [];
|
|
96
|
+
const b = tags.find((t) => String(t).includes("限时免费") || String(t).toLowerCase().includes("free"));
|
|
97
|
+
if (!b) return "";
|
|
98
|
+
const part = String(b).split(":")[1];
|
|
99
|
+
return part ? ` [${part}]` : ` [${b}]`;
|
|
100
|
+
};
|
|
101
|
+
// 能力列:上下文 k + 📷识图 🧠推理 🔧工具调用(workbuddy 原生字段;其他供应商无这些字段则显示 —)
|
|
102
|
+
const fmtCaps = (m) => {
|
|
103
|
+
const ctx = Number(m.maxInputTokens ?? m.context_length) || null;
|
|
104
|
+
const reasoning = m.supportsReasoning ?? m.is_reasoning;
|
|
105
|
+
const hasAny = ctx || m.supportsImages != null || reasoning != null || m.supportsToolCall != null;
|
|
106
|
+
if (!hasAny) return "—";
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (ctx) parts.push(ctx >= 1_000_000 ? `${Math.round(ctx / 100000) / 10}M` : `${Math.round(ctx / 1000)}k`);
|
|
109
|
+
if (m.supportsImages && !m.disabledMultimodal) parts.push("📷");
|
|
110
|
+
if (reasoning) parts.push("🧠");
|
|
111
|
+
if (m.supportsToolCall) parts.push("🔧");
|
|
112
|
+
return parts.join(" ");
|
|
113
|
+
};
|
|
114
|
+
const idW = Math.max(22, ...all.map((m) => String(m.id).length)) + 2;
|
|
115
|
+
const priceW = Math.max(6, ...all.map((m) => fmtPrice(m).length)) + 2;
|
|
116
|
+
const capsW = Math.max(4, ...all.map((m) => fmtCaps(m).length)) + 2;
|
|
117
|
+
for (const m of all) {
|
|
118
|
+
const ok = markAllowed(m.id);
|
|
119
|
+
const price = fmtPrice(m);
|
|
120
|
+
const badge = fmtBadge(m);
|
|
121
|
+
const name = m.name ? ` ${m.name}` : "";
|
|
122
|
+
const blocked = ok ? "" : " [blocked — allowlist]";
|
|
123
|
+
const line = ` ${ok ? "✓" : "x"} ${String(m.id).padEnd(idW)}${String(price).padEnd(priceW)}${fmtCaps(m).padEnd(capsW)}${name}${badge}${blocked}`;
|
|
124
|
+
console.log(line);
|
|
125
|
+
}
|
|
126
|
+
if (!all.length) console.log(` (no models — check baseUrl/keys or try: curl ${baseUrl}/models)`);
|
|
127
|
+
else if (allowedCount === 0) console.log(` tip: all blocked — mslxdff -provider ${id} allowAny on 或 allowlist set <model...>`);
|
|
128
|
+
else if (allowedCount !== all.length) console.log(` tip: blocked 仅影响 /v1/chat 调用,展示已全量列出`);
|
|
129
|
+
if (id === "zcode") {
|
|
130
|
+
console.log(` tip: 目录/额度可用;模型调用需上游一次性验证码参数(x-aliyun-captcha-verify-param,Start Plan 通道),未启用验证码农场前会返回 403 security_reject(code 3007)— 详见 docs/adr/0038`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try { await provider.close?.(); } catch {}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.error(`could not list ${id} models: ${String(e?.message || e)}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// mslxdff -provider qwenwork login — 千问办公设备授权(PKCE + poll),对齐 qoder-login。
|
|
2
|
+
// 授权后落盘 auths/qwenwork-<uid>.json + state 双写,默认 allowAny=false 并种 3 个实测模型(ADR-0037)。
|
|
3
|
+
import { compatFetch } from "../../../compat.js";
|
|
4
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
5
|
+
import { newDeviceFlow, pollGrant, userInfo, accountContext, expiryUnix } from "../../../providers/qwenwork/upstream.js";
|
|
6
|
+
|
|
7
|
+
const POLL_MS = 3000;
|
|
8
|
+
const POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
export async function handleQwenworkLogin(id, sub, rest = [], deps = {}) {
|
|
11
|
+
if (normalizeProviderId(id) !== "qwenwork") return false;
|
|
12
|
+
if (sub !== "login" && sub !== "auth" && sub !== "oauth") return false;
|
|
13
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
14
|
+
const log = deps.log || ((m) => console.log(m));
|
|
15
|
+
|
|
16
|
+
const flow = await newDeviceFlow();
|
|
17
|
+
log("=".repeat(60));
|
|
18
|
+
log(" 千问办公(QwenWork)登录");
|
|
19
|
+
log("=".repeat(60));
|
|
20
|
+
log("");
|
|
21
|
+
log(" 1. 在浏览器打开下面链接,用已登录千问办公的账号确认授权");
|
|
22
|
+
log(" 2. 授权成功后会自动完成,不要关闭本终端");
|
|
23
|
+
log("");
|
|
24
|
+
log(` ${flow.url}`);
|
|
25
|
+
log("");
|
|
26
|
+
log("🔄 等待你授权(自动轮询,每 3 秒,最多 10 分钟)...");
|
|
27
|
+
|
|
28
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
29
|
+
let grant = null;
|
|
30
|
+
for (;;) {
|
|
31
|
+
if (Date.now() > deadline) {
|
|
32
|
+
console.error("\n❌ 授权超时(10 分钟),请重新运行 mslxdff -provider qwenwork login");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
let outcome;
|
|
36
|
+
try {
|
|
37
|
+
outcome = await pollGrant(flow.nonce, flow.verifier, fetchImpl);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(`\n❌ 轮询失败: ${e.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
if (!outcome.pending) { grant = outcome.grant; break; }
|
|
43
|
+
try { process.stdout.write("."); } catch {}
|
|
44
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
45
|
+
}
|
|
46
|
+
const accessToken = grant.token || grant.device_token;
|
|
47
|
+
const refreshToken = grant.refresh_token || "";
|
|
48
|
+
if (!accessToken) {
|
|
49
|
+
console.error("\n❌ 未获取到 token");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
log(`\n✅ 授权成功!token=${String(accessToken).slice(0, 12)}...`);
|
|
53
|
+
if (refreshToken) log(` refreshToken=${String(refreshToken).slice(0, 12)}...`);
|
|
54
|
+
|
|
55
|
+
let uid = String(grant.user_id || "");
|
|
56
|
+
let name = String(grant.user_name || "");
|
|
57
|
+
let email = "";
|
|
58
|
+
try {
|
|
59
|
+
const info = await userInfo(accessToken, fetchImpl);
|
|
60
|
+
if (info?.id) uid = String(info.id);
|
|
61
|
+
if (info?.name) name = String(info.name);
|
|
62
|
+
if (info?.email) email = String(info.email);
|
|
63
|
+
log(` UID: ${uid} 昵称: ${name || "(空)"} 邮箱: ${email || "(空)"}`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
log(` ⚠️ userinfo 失败: ${e.message}(用 grant 兜底)`);
|
|
66
|
+
if (!uid) uid = String(accessToken).slice(0, 8);
|
|
67
|
+
}
|
|
68
|
+
if (!uid) {
|
|
69
|
+
console.error("❌ 无法获取用户标识");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { saveQwenworkAccount } = await import("../../../providers/qwenwork/account-store.js");
|
|
74
|
+
const save = deps.saveAccount || saveQwenworkAccount;
|
|
75
|
+
const saved = await save({
|
|
76
|
+
uid,
|
|
77
|
+
accessToken,
|
|
78
|
+
refreshToken,
|
|
79
|
+
expiresAt: expiryUnix(grant),
|
|
80
|
+
name,
|
|
81
|
+
email,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
log("");
|
|
85
|
+
log("=".repeat(60));
|
|
86
|
+
log("✅ 登录完成!");
|
|
87
|
+
log(` UID: ${uid}`);
|
|
88
|
+
log(` 账号数: ${saved.accounts}${saved.updated ? "(旧号凭证已更新)" : ""}`);
|
|
89
|
+
log(` 数据文件: ${saved.file}`);
|
|
90
|
+
log("=".repeat(60));
|
|
91
|
+
// 安全默认:allowAny 关 + 只放 3 个实测模型(与 qoder 惯例故意不同,见 ADR-0037)
|
|
92
|
+
try {
|
|
93
|
+
const { saveProviderAllowAnyModels, saveProviderAllowedModels } = await import("../../../state.js");
|
|
94
|
+
saveProviderAllowAnyModels("qwenwork", false);
|
|
95
|
+
saveProviderAllowedModels("qwenwork", ["flash", "pro", "qwen3.8-max-preview"]);
|
|
96
|
+
log(" allowAnyModels=off + allowlist 已种 flash/pro/qwen3.8-max-preview(额度池供应商,防 auto 烧分)");
|
|
97
|
+
} catch {}
|
|
98
|
+
// 额度展示
|
|
99
|
+
try {
|
|
100
|
+
const ctx = await accountContext(accessToken, fetchImpl);
|
|
101
|
+
const plan = ctx?.plan || {};
|
|
102
|
+
const quota = ctx?.quota || {};
|
|
103
|
+
if (plan?.name || quota?.remaining !== undefined) {
|
|
104
|
+
log(` 套餐: ${plan?.name || "-"} 积分剩余: ${quota?.remaining ?? "-"}`);
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
log(" 额度查询失败,不影响使用");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
log("下一步:");
|
|
111
|
+
log(" mslxdff -provider qwenwork models 列模型列表");
|
|
112
|
+
log(" mslxdff -restart(用户终端跑) 重启网关使新账号生效");
|
|
113
|
+
log(" 然后 curl http://localhost:8989/v1/chat/completions -d '{\"model\":\"qwenwork/flash\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'");
|
|
114
|
+
log("");
|
|
115
|
+
log("多账号:重复 `mslxdff -provider qwenwork login` 追加,每号独立 uid,对话自动轮换");
|
|
116
|
+
|
|
117
|
+
if (!deps.noExit) process.exit(0);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// mslxdff -provider zcode login — ZCode CLI 轮询授权(zai/bigmodel 双入口)。
|
|
2
|
+
// 授权后落盘 auths/zcode-<uid>.json(0600)+ state keys,并把内置目录补齐进 allowlist(开箱可用)。
|
|
3
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
4
|
+
import { compatFetch, uuid } from "../../../compat.js";
|
|
5
|
+
import { zcodeAppVersion } from "../../../providers/zcode/const.js";
|
|
6
|
+
import { tokenFingerprint } from "../../../providers/zcode/auth.js";
|
|
7
|
+
import { initZcodeFlow, waitForZcodeLogin } from "../../../providers/zcode/oauth.js";
|
|
8
|
+
import { listZcodeAccountDocs, saveZcodeAccount } from "../../../providers/zcode/account-store.js";
|
|
9
|
+
import { seedZcodeAllowlist } from "../../../providers/zcode/models.js";
|
|
10
|
+
|
|
11
|
+
const LOGIN_SUBS = new Set(["login", "auth", "oauth"]);
|
|
12
|
+
|
|
13
|
+
export async function handleZcodeLogin(id, sub, rest = [], deps = {}) {
|
|
14
|
+
if (normalizeProviderId(id) !== "zcode") return false;
|
|
15
|
+
if (!LOGIN_SUBS.has(sub)) return false;
|
|
16
|
+
|
|
17
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
18
|
+
const log = deps.log || ((m) => console.log(m));
|
|
19
|
+
const exit = deps.exit || ((code) => process.exit(code));
|
|
20
|
+
const provider = rest.includes("--bigmodel") || rest.includes("--bm") ? "bigmodel" : "zai";
|
|
21
|
+
|
|
22
|
+
// deviceMid:显式 > 既有 zcode 账号复用(设备身份跟账号走)> 临时生成(登录成功随账号落盘)。
|
|
23
|
+
const existingMid = (() => {
|
|
24
|
+
try {
|
|
25
|
+
const docs = listZcodeAccountDocs(deps.dir ? { dirs: [deps.dir] } : {});
|
|
26
|
+
return docs.map((d) => d.deviceMid).find(Boolean) || "";
|
|
27
|
+
} catch {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
const deviceMid = deps.deviceMid || existingMid || uuid();
|
|
32
|
+
|
|
33
|
+
log("=".repeat(60));
|
|
34
|
+
log(" ZCode 登录(智谱免费额度)");
|
|
35
|
+
log(` 入口: ${provider === "bigmodel" ? "BigModel(bigmodel.cn)" : "Z.ai(chat.z.ai)"}`);
|
|
36
|
+
log("=".repeat(60));
|
|
37
|
+
|
|
38
|
+
let out;
|
|
39
|
+
try {
|
|
40
|
+
const flow = await initZcodeFlow({ provider, fetchImpl, deviceMid, version: zcodeAppVersion() });
|
|
41
|
+
log("");
|
|
42
|
+
log(" 1. 在浏览器打开下面链接,用智谱账号登录并完成授权");
|
|
43
|
+
log(" 2. 授权完成后本机自动继续(CLI 轮询,无需回调)");
|
|
44
|
+
log("");
|
|
45
|
+
log(` ${flow.authorizeUrl}`);
|
|
46
|
+
log("");
|
|
47
|
+
log(`🔄 等待授权(每 ${Math.round(flow.pollIntervalMs / 1000)}s 轮询,最长 ${Math.max(1, Math.round((flow.expiresAtMs - Date.now()) / 1000))}s)...`);
|
|
48
|
+
out = await waitForZcodeLogin({ flow, fetchImpl, log, sleepFn: deps.sleepFn });
|
|
49
|
+
} catch (e) {
|
|
50
|
+
log("");
|
|
51
|
+
log(`❌ ${e?.message || e}`);
|
|
52
|
+
log(` (可重试:mslxdff -provider zcode login${provider === "bigmodel" ? " --bigmodel" : ""})`);
|
|
53
|
+
exit(1);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const uid = out.user.userId;
|
|
58
|
+
const save = deps.saveAccount || saveZcodeAccount;
|
|
59
|
+
const saved = await save({
|
|
60
|
+
uid,
|
|
61
|
+
jwt: out.jwt,
|
|
62
|
+
name: out.user.name,
|
|
63
|
+
email: out.user.email,
|
|
64
|
+
provider,
|
|
65
|
+
deviceMid,
|
|
66
|
+
dir: deps.dir,
|
|
67
|
+
file: deps.file,
|
|
68
|
+
});
|
|
69
|
+
const allow = seedZcodeAllowlist({ file: deps.file });
|
|
70
|
+
|
|
71
|
+
log("");
|
|
72
|
+
log(`✅ 授权成功!uid=${uid} 昵称=${out.user.name || "(空)"} token=${tokenFingerprint(out.jwt)}…`);
|
|
73
|
+
log(` 凭据: ${saved.file}`);
|
|
74
|
+
if (allow.added.length) log(` allowlist 已补齐 ${allow.added.length} 个免费模型:${allow.added.join(", ")}`);
|
|
75
|
+
log(" 下一步: mslxdff -provider zcode quota 查看额度 · mslxdff -provider zcode models 查看模型");
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// mslxdff -provider zcode quota — 查询 ZCode 免费/订阅额度(billing/balance)。
|
|
2
|
+
// 未登录 → login 指引 + 非零退出;401 → 重登指引;空套餐 → 领取指引(全人话,不出堆栈)。
|
|
3
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
4
|
+
import { compatFetch } from "../../../compat.js";
|
|
5
|
+
import { loadProviderKeys } from "../../../state.js";
|
|
6
|
+
import { listZcodeAccountDocs } from "../../../providers/zcode/account-store.js";
|
|
7
|
+
import { zcodeAppVersion } from "../../../providers/zcode/const.js";
|
|
8
|
+
import { fetchZcodeBalance, formatZcodeQuota } from "../../../providers/zcode/quota.js";
|
|
9
|
+
|
|
10
|
+
export async function handleZcodeQuota(id, sub, rest = [], deps = {}) {
|
|
11
|
+
if (normalizeProviderId(id) !== "zcode") return false;
|
|
12
|
+
if (String(sub || "").toLowerCase() !== "quota") return false;
|
|
13
|
+
|
|
14
|
+
const fetchImpl = deps.fetchImpl || compatFetch;
|
|
15
|
+
const log = deps.log || ((m) => console.log(m));
|
|
16
|
+
const exit = deps.exit || ((code) => process.exit(code));
|
|
17
|
+
const wantsJson = rest.includes("--json") || rest.includes("-json");
|
|
18
|
+
|
|
19
|
+
let keys = [];
|
|
20
|
+
try {
|
|
21
|
+
keys = loadProviderKeys("zcode", deps.file ? { file: deps.file } : {});
|
|
22
|
+
} catch {
|
|
23
|
+
keys = [];
|
|
24
|
+
}
|
|
25
|
+
if (!keys.length) {
|
|
26
|
+
log("zcode quota:尚未登录。先运行:");
|
|
27
|
+
log(" mslxdff -provider zcode login");
|
|
28
|
+
log("(授权后即可查看 Start/Coding Plan 额度,全程无需验证码)");
|
|
29
|
+
exit(1);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const token = keys[0];
|
|
34
|
+
const deviceMid = deps.deviceMid || (() => {
|
|
35
|
+
try {
|
|
36
|
+
return listZcodeAccountDocs(deps.dir ? { dirs: [deps.dir] } : {}).map((d) => d.deviceMid).find(Boolean) || "";
|
|
37
|
+
} catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
const result = await fetchZcodeBalance({ token, deviceMid, fetchImpl, version: deps.version || zcodeAppVersion() });
|
|
42
|
+
|
|
43
|
+
if (wantsJson) {
|
|
44
|
+
const payload = result.ok
|
|
45
|
+
? { provider: "zcode", keys: keys.length, ok: true, parsed: result.parsed }
|
|
46
|
+
: { provider: "zcode", keys: keys.length, ok: false, kind: result.kind, code: result.code, message: result.message };
|
|
47
|
+
log(JSON.stringify(payload, null, 2));
|
|
48
|
+
exit(result.ok ? 0 : 1);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
log(formatZcodeQuota(result));
|
|
52
|
+
if (!result.ok) log(` (已登录账号 ${keys.length} 个;换号/重登:mslxdff -provider zcode login)`);
|
|
53
|
+
exit(result.ok ? 0 : 1);
|
|
54
|
+
return true;
|
|
55
|
+
}
|