mslxdff 0.1.174 → 0.1.176
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 +3 -3
- 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/auto-race.js +26 -1
- package/src/chat-pipeline/index.js +131 -131
- package/src/chat-pipeline/policy.js +84 -84
- package/src/chat-pipeline/serial-trial.js +35 -8
- package/src/chat-pipeline/terminal-verdict.js +109 -0
- package/src/cli/commands/group.js +249 -249
- 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 +136 -133
- package/src/cli/commands/provider/keys-guard.js +50 -0
- package/src/cli/commands/provider/keys.js +7 -0
- package/src/cli/commands/provider/models.js +145 -145
- package/src/cli/commands/provider/qwenwork-login.js +119 -119
- package/src/cli/commands/provider/traework-checkin.js +144 -0
- package/src/cli/commands/provider/traework-login.js +7 -45
- package/src/cli/commands/sync.js +310 -310
- package/src/cli/help.js +3 -0
- package/src/cli/status.js +279 -279
- package/src/daemon.js +112 -112
- 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 +2 -0
- package/src/models.js +225 -225
- 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/codearts/chat.js +7 -1
- package/src/providers/codearts/const.js +6 -1
- package/src/providers/codearts/index.js +18 -11
- package/src/providers/globalqwenwork/cosy.js +123 -123
- package/src/providers/globalqwenwork/crypto.js +218 -218
- package/src/providers/globalqwenwork/http.js +20 -20
- package/src/providers/globalqwenwork/rsa.js +54 -54
- package/src/providers/globalqwenwork/sse.js +268 -268
- package/src/providers/globalqwenwork/stream.js +130 -130
- package/src/providers/globalqwenwork/upstream.js +122 -122
- package/src/providers/keyring.js +60 -60
- package/src/providers/qoder/chat.js +203 -203
- package/src/providers/qoder/index.js +249 -249
- package/src/providers/qoder/sse.js +145 -145
- package/src/providers/qwenwork/account-store.js +133 -133
- package/src/providers/qwenwork/constants.js +67 -67
- package/src/providers/qwenwork/cosy.js +120 -120
- package/src/providers/qwenwork/crypto.js +218 -218
- package/src/providers/qwenwork/http.js +20 -20
- package/src/providers/qwenwork/index.js +327 -327
- package/src/providers/qwenwork/payload.js +144 -144
- package/src/providers/qwenwork/rsa.js +54 -54
- package/src/providers/qwenwork/sse.js +268 -268
- package/src/providers/qwenwork/stream.js +130 -130
- package/src/providers/qwenwork/upstream.js +120 -120
- package/src/providers/qwenwork.js +1 -1
- package/src/providers/registry.js +79 -79
- package/src/providers/traework/account-store.js +87 -2
- package/src/providers/traework/checkin.js +51 -9
- package/src/providers/traework/constants.js +20 -0
- package/src/providers/traework/headers.js +42 -10
- package/src/providers/traework/index.js +14 -4
- package/src/providers/traework/market-identity.js +116 -0
- 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/reasoning.js +32 -32
- package/src/routes/chat/gateway.js +46 -46
- package/src/routes/chat/relay-pipeline.js +294 -294
- package/src/routes/chat/via-route-handler.js +146 -146
- 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 +385 -385
- package/src/runtime/bootstrap.js +50 -50
- package/src/runtime/provider-gate.js +36 -36
- package/src/runtime/providers-setup.js +168 -165
- package/src/runtime/traework-checkin.js +26 -11
- package/src/server.js +69 -69
- 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 +291 -289
- package/src/warp-pool/config.js +71 -0
- package/src/warp-pool/health.js +65 -0
- package/src/warp-pool/index.js +261 -0
- package/src/warp-pool/keys.js +29 -0
- package/src/warp-pool/nodes.js +94 -0
- package/src/warp-pool/register.js +113 -0
- package/src/warp-pool/rotate.js +150 -0
- package/src/warp-pool/strategy.js +43 -0
- package/src/warp-pool/wireproxy.js +153 -0
- package/docs/cli_help_mini.md +0 -143
- package/src/providers/AGENTS.md +0 -49
- package/src/routes/AGENTS.md +0 -37
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// 覆盖式写 provider keys 前的最后一道闸(单一判据来源,keys.js 只管调用)。
|
|
2
|
+
//
|
|
3
|
+
// 事故原型 2026-10-15:`mslxdff -provider traework quota` 当时 traework 没有 quota 处理器,
|
|
4
|
+
// `quota` 一路掉进 keys.js 的「未知即当 key」兜底分支,把 providerConfigs.traework.keys[0]
|
|
5
|
+
// 那个 1004 字符的 accessToken 整组覆盖成字符串 "quota" —— auth 号型的 keys 就是 accessToken 本身,
|
|
6
|
+
// 等于当场废号;且 keys 与 auths 长度仍相等(1:1),绕过了 provider 侧「长度不配对才回填」的自愈判据。
|
|
7
|
+
import { normalizeProviderId } from "../../../providers/model-id.js";
|
|
8
|
+
import { AUTH_DOC_PROVIDER_IDS } from "../../../runtime/provider-gate.js";
|
|
9
|
+
|
|
10
|
+
// 保留子命令:这些词归 provider/ 下别的 handler 管,掉到 keys 兜底 = 该 provider 没接线。
|
|
11
|
+
// 名单 = 各 handler 实际比对的 sub 值全集(keys.js 自己认的 list/status/add/remove/clear 不在内)。
|
|
12
|
+
// 新增 handler 时同步这里;漏收的后果是那个词能被当 key 写盘。
|
|
13
|
+
export const RESERVED_PROVIDER_SUBS = new Set([
|
|
14
|
+
"login", "auth", "oauth", "checkin", "signin", "quota", "credits", "balance",
|
|
15
|
+
"migrate", "free", "bench", "benchmark", "eval", "test",
|
|
16
|
+
"models", "show-models", "list-models", "ls", "models-path", "chat-path",
|
|
17
|
+
"set-url", "set-models-path", "set-chat-path", "url", "seturl", "setmodelspath", "setchatpath",
|
|
18
|
+
"allowlist", "allow", "allowed", "allowany", "allow-any", "allow_any", "whitelist",
|
|
19
|
+
"del", "delete", "rm", "remove-provider", "del-provider", "import", "import-desktop", "from-desktop",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 判断这次「把参数当 keys 整组覆盖」要不要拦。
|
|
24
|
+
* @returns null = 放行;string[] = 拒写,元素是给人看的报错行(调用方打印后 exit 1,且必须已改盘前退出)
|
|
25
|
+
*/
|
|
26
|
+
export function keysWriteDenial(id, sub, rest = []) {
|
|
27
|
+
const pid = normalizeProviderId(id) || String(id || "").toLowerCase();
|
|
28
|
+
const subLc = String(sub || "").toLowerCase();
|
|
29
|
+
|
|
30
|
+
// 闸 1:verb 一律不当 key(无 --force 逃生阀,真要写这个词走 `add`)
|
|
31
|
+
if (RESERVED_PROVIDER_SUBS.has(subLc)) {
|
|
32
|
+
return [
|
|
33
|
+
`✗ -provider ${id} ${sub}:该供应商没有这个子命令 —— 已拒绝写 keys,配置未改动`,
|
|
34
|
+
` 拦截原因:落到 keys 分支会把 "${sub}" 当成 API key 覆盖 ${id} 现有 keys。`,
|
|
35
|
+
` ${id} 支持什么:mslxdff -provider ${id} list`,
|
|
36
|
+
` 真要把某个词当 key(罕见):mslxdff -provider ${id} add ${sub}`,
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
// 闸 2:auth 号型的 keys 由 login / account-store 派生、与 auths[] 按位序平行
|
|
40
|
+
// (raccoon 更由 syncRaccoonKeys 覆盖式同步),手填必错且下次登录即被冲掉 → 只留 --force 逃生阀。
|
|
41
|
+
if (AUTH_DOC_PROVIDER_IDS.includes(pid) && !rest.includes("--force")) {
|
|
42
|
+
return [
|
|
43
|
+
`✗ 已拒绝覆盖 ${pid} 的 keys(配置未改动)`,
|
|
44
|
+
` ${pid} 是 auth 号型供应商:keys 存的是 login 落盘的 accessToken,与 auths[] 按位序成对,手填等于废号。`,
|
|
45
|
+
` 正确姿势: mslxdff -provider ${pid} login(登录后自动回填 keys/auths)`,
|
|
46
|
+
` 确知自己在做什么 → 末尾加 --force 重跑`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { keysWriteDenial } from "./keys-guard.js";
|
|
2
|
+
|
|
1
3
|
export async function handleProviderKeys(id, sub, rest, args) {
|
|
2
4
|
if (sub === "list" || sub === "status") {
|
|
3
5
|
const { loadProviderKeys, loadProviderConfig, loadProviderAllowedModels, loadProviderAllowAnyModels } = await import("../../../state.js");
|
|
@@ -108,6 +110,11 @@ export async function handleProviderKeys(id, sub, rest, args) {
|
|
|
108
110
|
process.exit(0);
|
|
109
111
|
}
|
|
110
112
|
if (sub && !sub.startsWith("-")) {
|
|
113
|
+
const denial = keysWriteDenial(id, sub, rest);
|
|
114
|
+
if (denial) {
|
|
115
|
+
for (const l of denial) console.error(l);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
111
118
|
const { loadProviderKeys, loadProviderConfig, saveProviderConfig, saveProviderKeys, loadProviderConfigs } = await import("../../../state.js");
|
|
112
119
|
const keys = rest.filter((k) => !k.startsWith("-"));
|
|
113
120
|
if (!keys.length) {
|
|
@@ -1,145 +1,145 @@
|
|
|
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 === "globalqwenwork") {
|
|
57
|
-
const { createGlobalQwenworkProvider } = await import("../../../providers/globalqwenwork/index.js");
|
|
58
|
-
provider = createGlobalQwenworkProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
59
|
-
} else if (id === "zcode") {
|
|
60
|
-
const { createZcodeProvider } = await import("../../../providers/zcode/index.js");
|
|
61
|
-
provider = createZcodeProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
62
|
-
} else if (id === "raccoon") {
|
|
63
|
-
const { createRaccoonProvider } = await import("../../../providers/raccoon/index.js");
|
|
64
|
-
provider = createRaccoonProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
65
|
-
} else {
|
|
66
|
-
if (!baseUrl) {
|
|
67
|
-
console.error(`provider ${id}: missing baseUrl — set via: mslxdff -provider ${id} set-url <baseUrl>`);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
provider = createGenericProvider({ id, baseUrl, apiKeys: keys, file: defaultStateFile() });
|
|
71
|
-
}
|
|
72
|
-
const all = await provider.listModels();
|
|
73
|
-
const markAllowed = (mid) => {
|
|
74
|
-
const raw = String(mid || "").includes("/") ? String(mid).split("/").slice(1).join("/") : String(mid);
|
|
75
|
-
const checkRaw = mid.startsWith(`${id}/`) ? mid.slice(id.length + 1) : raw;
|
|
76
|
-
return isModelAllowed(id, checkRaw);
|
|
77
|
-
};
|
|
78
|
-
if (wantsJson) {
|
|
79
|
-
const filtered = all.filter((m) => markAllowed(m.id));
|
|
80
|
-
console.log(JSON.stringify({ object: "list", data: filtered }, null, 2));
|
|
81
|
-
} else {
|
|
82
|
-
const allowedCount = all.filter((m) => markAllowed(m.id)).length;
|
|
83
|
-
console.log(`${id} models (${all.length} total, ${allowedCount} ✓ allowed${allowedCount !== all.length ? `, ${all.length - allowedCount} x blocked by allowlist` : ""}):`);
|
|
84
|
-
const fmtPrice = (m) => {
|
|
85
|
-
const c = String(m.credits || "").trim();
|
|
86
|
-
if (c) {
|
|
87
|
-
const m0 = c.match(/x\s*([\d.]+)/i);
|
|
88
|
-
if (m0) return `x${m0[1]}`;
|
|
89
|
-
return c.replace(/\s*credits\s*/gi, "").trim().replace(/\s+/g, " ");
|
|
90
|
-
}
|
|
91
|
-
if (m.pricing && typeof m.pricing === "object") {
|
|
92
|
-
const p = m.pricing.prompt ?? m.pricing.input ?? m.pricing.completion ?? "";
|
|
93
|
-
if (p) return String(p);
|
|
94
|
-
}
|
|
95
|
-
if (m.price_factor != null && String(m.price_factor).trim() !== "") return `x${m.price_factor}`;
|
|
96
|
-
if (m.price != null && String(m.price).trim()) return String(m.price).trim();
|
|
97
|
-
if (String(m.id).endsWith("/auto")) return "浮动";
|
|
98
|
-
return "—";
|
|
99
|
-
};
|
|
100
|
-
const fmtBadge = (m) => {
|
|
101
|
-
const tags = Array.isArray(m.tags) ? m.tags : [];
|
|
102
|
-
const b = tags.find((t) => String(t).includes("限时免费") || String(t).toLowerCase().includes("free"));
|
|
103
|
-
if (!b) return "";
|
|
104
|
-
const part = String(b).split(":")[1];
|
|
105
|
-
return part ? ` [${part}]` : ` [${b}]`;
|
|
106
|
-
};
|
|
107
|
-
// 能力列:上下文 k + 📷识图 🧠推理 🔧工具调用(workbuddy 原生字段;其他供应商无这些字段则显示 —)
|
|
108
|
-
const fmtCaps = (m) => {
|
|
109
|
-
const ctx = Number(m.maxInputTokens ?? m.context_length) || null;
|
|
110
|
-
const reasoning = m.supportsReasoning ?? m.is_reasoning;
|
|
111
|
-
const hasAny = ctx || m.supportsImages != null || reasoning != null || m.supportsToolCall != null;
|
|
112
|
-
if (!hasAny) return "—";
|
|
113
|
-
const parts = [];
|
|
114
|
-
if (ctx) parts.push(ctx >= 1_000_000 ? `${Math.round(ctx / 100000) / 10}M` : `${Math.round(ctx / 1000)}k`);
|
|
115
|
-
if (m.supportsImages && !m.disabledMultimodal) parts.push("📷");
|
|
116
|
-
if (reasoning) parts.push("🧠");
|
|
117
|
-
if (m.supportsToolCall) parts.push("🔧");
|
|
118
|
-
return parts.join(" ");
|
|
119
|
-
};
|
|
120
|
-
const idW = Math.max(22, ...all.map((m) => String(m.id).length)) + 2;
|
|
121
|
-
const priceW = Math.max(6, ...all.map((m) => fmtPrice(m).length)) + 2;
|
|
122
|
-
const capsW = Math.max(4, ...all.map((m) => fmtCaps(m).length)) + 2;
|
|
123
|
-
for (const m of all) {
|
|
124
|
-
const ok = markAllowed(m.id);
|
|
125
|
-
const price = fmtPrice(m);
|
|
126
|
-
const badge = fmtBadge(m);
|
|
127
|
-
const name = m.name ? ` ${m.name}` : "";
|
|
128
|
-
const blocked = ok ? "" : " [blocked — allowlist]";
|
|
129
|
-
const line = ` ${ok ? "✓" : "x"} ${String(m.id).padEnd(idW)}${String(price).padEnd(priceW)}${fmtCaps(m).padEnd(capsW)}${name}${badge}${blocked}`;
|
|
130
|
-
console.log(line);
|
|
131
|
-
}
|
|
132
|
-
if (!all.length) console.log(` (no models — check baseUrl/keys or try: curl ${baseUrl}/models)`);
|
|
133
|
-
else if (allowedCount === 0) console.log(` tip: all blocked — mslxdff -provider ${id} allowAny on 或 allowlist set <model...>`);
|
|
134
|
-
else if (allowedCount !== all.length) console.log(` tip: blocked 仅影响 /v1/chat 调用,展示已全量列出`);
|
|
135
|
-
if (id === "zcode") {
|
|
136
|
-
console.log(` tip: 目录/额度/模型调用全通(上游 v3.14.4 起模型请求免验证码,docs/adr/0038);额度耗尽返回 429 quota_exhausted,按日窗口重置`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
try { await provider.close?.(); } catch {}
|
|
140
|
-
} catch (e) {
|
|
141
|
-
console.error(`could not list ${id} models: ${String(e?.message || e)}`);
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
process.exit(0);
|
|
145
|
-
}
|
|
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 === "globalqwenwork") {
|
|
57
|
+
const { createGlobalQwenworkProvider } = await import("../../../providers/globalqwenwork/index.js");
|
|
58
|
+
provider = createGlobalQwenworkProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
59
|
+
} else if (id === "zcode") {
|
|
60
|
+
const { createZcodeProvider } = await import("../../../providers/zcode/index.js");
|
|
61
|
+
provider = createZcodeProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
62
|
+
} else if (id === "raccoon") {
|
|
63
|
+
const { createRaccoonProvider } = await import("../../../providers/raccoon/index.js");
|
|
64
|
+
provider = createRaccoonProvider({ id, apiKeys: keys, file: defaultStateFile() });
|
|
65
|
+
} else {
|
|
66
|
+
if (!baseUrl) {
|
|
67
|
+
console.error(`provider ${id}: missing baseUrl — set via: mslxdff -provider ${id} set-url <baseUrl>`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
provider = createGenericProvider({ id, baseUrl, apiKeys: keys, file: defaultStateFile() });
|
|
71
|
+
}
|
|
72
|
+
const all = await provider.listModels();
|
|
73
|
+
const markAllowed = (mid) => {
|
|
74
|
+
const raw = String(mid || "").includes("/") ? String(mid).split("/").slice(1).join("/") : String(mid);
|
|
75
|
+
const checkRaw = mid.startsWith(`${id}/`) ? mid.slice(id.length + 1) : raw;
|
|
76
|
+
return isModelAllowed(id, checkRaw);
|
|
77
|
+
};
|
|
78
|
+
if (wantsJson) {
|
|
79
|
+
const filtered = all.filter((m) => markAllowed(m.id));
|
|
80
|
+
console.log(JSON.stringify({ object: "list", data: filtered }, null, 2));
|
|
81
|
+
} else {
|
|
82
|
+
const allowedCount = all.filter((m) => markAllowed(m.id)).length;
|
|
83
|
+
console.log(`${id} models (${all.length} total, ${allowedCount} ✓ allowed${allowedCount !== all.length ? `, ${all.length - allowedCount} x blocked by allowlist` : ""}):`);
|
|
84
|
+
const fmtPrice = (m) => {
|
|
85
|
+
const c = String(m.credits || "").trim();
|
|
86
|
+
if (c) {
|
|
87
|
+
const m0 = c.match(/x\s*([\d.]+)/i);
|
|
88
|
+
if (m0) return `x${m0[1]}`;
|
|
89
|
+
return c.replace(/\s*credits\s*/gi, "").trim().replace(/\s+/g, " ");
|
|
90
|
+
}
|
|
91
|
+
if (m.pricing && typeof m.pricing === "object") {
|
|
92
|
+
const p = m.pricing.prompt ?? m.pricing.input ?? m.pricing.completion ?? "";
|
|
93
|
+
if (p) return String(p);
|
|
94
|
+
}
|
|
95
|
+
if (m.price_factor != null && String(m.price_factor).trim() !== "") return `x${m.price_factor}`;
|
|
96
|
+
if (m.price != null && String(m.price).trim()) return String(m.price).trim();
|
|
97
|
+
if (String(m.id).endsWith("/auto")) return "浮动";
|
|
98
|
+
return "—";
|
|
99
|
+
};
|
|
100
|
+
const fmtBadge = (m) => {
|
|
101
|
+
const tags = Array.isArray(m.tags) ? m.tags : [];
|
|
102
|
+
const b = tags.find((t) => String(t).includes("限时免费") || String(t).toLowerCase().includes("free"));
|
|
103
|
+
if (!b) return "";
|
|
104
|
+
const part = String(b).split(":")[1];
|
|
105
|
+
return part ? ` [${part}]` : ` [${b}]`;
|
|
106
|
+
};
|
|
107
|
+
// 能力列:上下文 k + 📷识图 🧠推理 🔧工具调用(workbuddy 原生字段;其他供应商无这些字段则显示 —)
|
|
108
|
+
const fmtCaps = (m) => {
|
|
109
|
+
const ctx = Number(m.maxInputTokens ?? m.context_length) || null;
|
|
110
|
+
const reasoning = m.supportsReasoning ?? m.is_reasoning;
|
|
111
|
+
const hasAny = ctx || m.supportsImages != null || reasoning != null || m.supportsToolCall != null;
|
|
112
|
+
if (!hasAny) return "—";
|
|
113
|
+
const parts = [];
|
|
114
|
+
if (ctx) parts.push(ctx >= 1_000_000 ? `${Math.round(ctx / 100000) / 10}M` : `${Math.round(ctx / 1000)}k`);
|
|
115
|
+
if (m.supportsImages && !m.disabledMultimodal) parts.push("📷");
|
|
116
|
+
if (reasoning) parts.push("🧠");
|
|
117
|
+
if (m.supportsToolCall) parts.push("🔧");
|
|
118
|
+
return parts.join(" ");
|
|
119
|
+
};
|
|
120
|
+
const idW = Math.max(22, ...all.map((m) => String(m.id).length)) + 2;
|
|
121
|
+
const priceW = Math.max(6, ...all.map((m) => fmtPrice(m).length)) + 2;
|
|
122
|
+
const capsW = Math.max(4, ...all.map((m) => fmtCaps(m).length)) + 2;
|
|
123
|
+
for (const m of all) {
|
|
124
|
+
const ok = markAllowed(m.id);
|
|
125
|
+
const price = fmtPrice(m);
|
|
126
|
+
const badge = fmtBadge(m);
|
|
127
|
+
const name = m.name ? ` ${m.name}` : "";
|
|
128
|
+
const blocked = ok ? "" : " [blocked — allowlist]";
|
|
129
|
+
const line = ` ${ok ? "✓" : "x"} ${String(m.id).padEnd(idW)}${String(price).padEnd(priceW)}${fmtCaps(m).padEnd(capsW)}${name}${badge}${blocked}`;
|
|
130
|
+
console.log(line);
|
|
131
|
+
}
|
|
132
|
+
if (!all.length) console.log(` (no models — check baseUrl/keys or try: curl ${baseUrl}/models)`);
|
|
133
|
+
else if (allowedCount === 0) console.log(` tip: all blocked — mslxdff -provider ${id} allowAny on 或 allowlist set <model...>`);
|
|
134
|
+
else if (allowedCount !== all.length) console.log(` tip: blocked 仅影响 /v1/chat 调用,展示已全量列出`);
|
|
135
|
+
if (id === "zcode") {
|
|
136
|
+
console.log(` tip: 目录/额度/模型调用全通(上游 v3.14.4 起模型请求免验证码,docs/adr/0038);额度耗尽返回 429 quota_exhausted,按日窗口重置`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
try { await provider.close?.(); } catch {}
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error(`could not list ${id} models: ${String(e?.message || e)}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
@@ -1,119 +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
|
-
}
|
|
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
|
+
}
|