dsh-codearts 0.5.1 → 0.5.3
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/lib/bin.mjs +9 -21
- package/lib/client/index.d.cts +8 -1
- package/lib/client/index.js +133 -52
- package/lib/index.d.mts +21 -15
- package/lib/index.mjs +160 -54
- package/lib/{usage-B4vQb1sP.mjs → usage-Djtou78p.mjs} +220 -44
- package/package.json +2 -1
package/lib/bin.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { A as isBenefitModel, N as codeartsAccountsPath, j as CodeArtsAccountStore, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker, u as fetchCallerIdentity } from "./usage-Djtou78p.mjs";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
//#region src/version.ts
|
|
5
|
-
const CODEARTS_VERSION = "0.5.
|
|
5
|
+
const CODEARTS_VERSION = "0.5.3";
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region src/bin.ts
|
|
8
8
|
/**
|
|
@@ -38,7 +38,7 @@ const CODEARTS_VERSION = "0.5.1";
|
|
|
38
38
|
* @module dsh-codearts/bin
|
|
39
39
|
*/
|
|
40
40
|
/** Bumped when a `--json` document changes shape (5: identity backfill). */
|
|
41
|
-
const JSON_SCHEMA_VERSION =
|
|
41
|
+
const JSON_SCHEMA_VERSION = 6;
|
|
42
42
|
/**
|
|
43
43
|
* 一句话描述一个账号,供人类可读输出用。
|
|
44
44
|
*
|
|
@@ -70,11 +70,7 @@ async function status(jsonOutput) {
|
|
|
70
70
|
version: CODEARTS_VERSION,
|
|
71
71
|
signedIn: accounts.length > 0,
|
|
72
72
|
signable: accounts.some((account) => account.signable),
|
|
73
|
-
accounts
|
|
74
|
-
models: FALLBACK_CODEARTS_MODELS.map((m) => ({
|
|
75
|
-
id: m.id,
|
|
76
|
-
name: m.name
|
|
77
|
-
}))
|
|
73
|
+
accounts
|
|
78
74
|
};
|
|
79
75
|
if (cred !== void 0) {
|
|
80
76
|
if (cred.label !== void 0) out.label = cred.label;
|
|
@@ -91,15 +87,14 @@ async function status(jsonOutput) {
|
|
|
91
87
|
for (const account of accounts) process.stdout.write(` - ${describeAccount(account)}\n`);
|
|
92
88
|
if (!out.signable) process.stdout.write("warning: no account carries AK/SK, so requests cannot be signed and chats will be rejected\n");
|
|
93
89
|
} else process.stdout.write("not signed in (no account stored; sign in from the CodeArts settings card)\n");
|
|
94
|
-
process.stdout.write(`models
|
|
95
|
-
for (const m of out.models) process.stdout.write(` - ${m.id} ${m.name}\n`);
|
|
90
|
+
process.stdout.write("models: CLI 不缓存目录,跑 `dsh-codearts models` 现问上游\n");
|
|
96
91
|
return out.signedIn ? 0 : 1;
|
|
97
92
|
}
|
|
98
93
|
/**
|
|
99
94
|
* 动态发现当前可用模型(三路:内置 / 限时福利 / agent-center)。
|
|
100
95
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
96
|
+
* 这是排查「模型选择器里没有某个模型」的第一手段:它打印的是上游真实返回。
|
|
97
|
+
* 未登录时就是没有目录,直接说明原因 —— 不打印手维护的种子清单冒充上游结果。
|
|
103
98
|
*/
|
|
104
99
|
async function models(jsonOutput) {
|
|
105
100
|
if ((await store().list()).length === 0) {
|
|
@@ -108,16 +103,9 @@ async function models(jsonOutput) {
|
|
|
108
103
|
version: CODEARTS_VERSION,
|
|
109
104
|
discovered: false,
|
|
110
105
|
reason: "not signed in",
|
|
111
|
-
models:
|
|
112
|
-
id: m.id,
|
|
113
|
-
name: m.name,
|
|
114
|
-
benefit: isBenefitModel(m.id)
|
|
115
|
-
}))
|
|
106
|
+
models: []
|
|
116
107
|
}, null, 2) + "\n");
|
|
117
|
-
else
|
|
118
|
-
process.stdout.write("not signed in — showing the built-in seed catalog\n");
|
|
119
|
-
for (const m of FALLBACK_CODEARTS_MODELS) process.stdout.write(` - ${m.id} ${m.name}${isBenefitModel(m.id) ? " [benefit]" : ""}\n`);
|
|
120
|
-
}
|
|
108
|
+
else process.stdout.write("not signed in — 没有目录可展示(不会用种子清单冒充上游返回)\n");
|
|
121
109
|
return 1;
|
|
122
110
|
}
|
|
123
111
|
const outcome = await discoverModels(store());
|
package/lib/client/index.d.cts
CHANGED
|
@@ -34,7 +34,14 @@ interface CodeArtsSettingsKey {
|
|
|
34
34
|
optionalModelsAllHint: string;
|
|
35
35
|
selectedCount: string;
|
|
36
36
|
selectAll: string;
|
|
37
|
-
|
|
37
|
+
benefit: string;
|
|
38
|
+
benefitBlocked: string;
|
|
39
|
+
benefitBlockedHint: string;
|
|
40
|
+
refreshModels: string;
|
|
41
|
+
refreshing: string;
|
|
42
|
+
refreshed: string;
|
|
43
|
+
refreshStale: string;
|
|
44
|
+
refreshFailed: string;
|
|
38
45
|
saveModels: string;
|
|
39
46
|
saving: string;
|
|
40
47
|
saved: string;
|
package/lib/client/index.js
CHANGED
|
@@ -7,8 +7,23 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
-
//#region
|
|
11
|
-
|
|
10
|
+
//#region src/catalog.ts
|
|
11
|
+
/**
|
|
12
|
+
* 剥掉模型名尾巴上的标记:` · x0.18`、` · x0.00 · 福利`、`(福利)`、`(免费)`、
|
|
13
|
+
* `(x0.18)` 等(兼容旧格式)。
|
|
14
|
+
*/
|
|
15
|
+
function stripModelSuffixes(name) {
|
|
16
|
+
let base = name;
|
|
17
|
+
for (;;) {
|
|
18
|
+
const next = base.replace(/\s*·\s*(?:x\d+(?:\.\d+)?|(?:限时)?(?:免费|福利)(?:\s+x\d+(?:\.\d+)?)?)\s*$/, "");
|
|
19
|
+
if (next === base) break;
|
|
20
|
+
base = next;
|
|
21
|
+
}
|
|
22
|
+
return base.replace(/\s*[((]\s*(?:(?:免费|福利)(?:\s+x\d+(?:\.\d+)?)?|x\d+(?:\.\d+)?)\s*[))]\s*$/, "").trim();
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region \0dsh-css:/home/runner/work/dsh-codearts/dsh-codearts/src/client/CodeArtsPluginCard.module.css.mjs
|
|
26
|
+
const css = ".xoziZG_card{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-settings-card-fill);max-width:720px}.xoziZG_header{align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.xoziZG_headText{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.xoziZG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:22px}.xoziZG_desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.xoziZG_statusLine{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;font-size:14px;font-weight:500;line-height:22px;display:inline-flex}.xoziZG_meta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px;line-height:18px}.xoziZG_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 16px}.xoziZG_section{margin-top:16px}.xoziZG_sectionHead{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.xoziZG_sectionLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500}.xoziZG_headActions{flex:none;align-items:center;gap:8px;display:flex}.xoziZG_count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.xoziZG_hint{color:var(--dsw-alias-label-secondary);margin:8px 0 0;font-size:13px;line-height:20px}.xoziZG_error{color:var(--dsw-alias-state-error-primary);margin:10px 0 0;font-size:13px;line-height:20px}.xoziZG_actions{flex-wrap:wrap;align-items:center;gap:8px;margin-top:12px;display:flex}.xoziZG_danger{color:var(--dsw-alias-state-error-primary)}.xoziZG_link{appearance:none;color:var(--dsw-alias-brand-text);font:inherit;text-align:left;word-break:break-all;cursor:pointer;background:0 0;border:none;padding:0;font-size:12px}.xoziZG_link:hover{text-decoration:underline}.xoziZG_choices{flex-direction:column;gap:2px;margin-top:8px;display:flex}.xoziZG_choiceRow{border-radius:var(--dsw-radius-sm);align-items:center;gap:8px;margin:0 -6px;padding:5px 6px;display:flex}.xoziZG_choiceRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.xoziZG_check{flex:1;min-width:0}.xoziZG_check>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.xoziZG_manual{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);margin-top:12px;padding:0 12px}.xoziZG_manual[open]{padding-bottom:12px}.xoziZG_manualSummary{color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:6px;padding:9px 0;font-size:13px;list-style:none;display:flex}.xoziZG_manualSummary::-webkit-details-marker{display:none}.xoziZG_field{flex-direction:column;gap:5px;margin-top:10px;display:flex}.xoziZG_fieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px}.xoziZG_area{box-sizing:border-box;resize:vertical;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:20px}.xoziZG_area::placeholder{color:var(--dsw-alias-label-dimmed)}.xoziZG_area:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}";
|
|
12
27
|
const tagId = "dsh-codearts/CodeArtsPluginCard.module.css";
|
|
13
28
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
14
29
|
const tag = document.createElement("style");
|
|
@@ -18,32 +33,32 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
|
|
|
18
33
|
document.head.appendChild(tag);
|
|
19
34
|
}
|
|
20
35
|
var CodeArtsPluginCard_module_css_default = {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"danger": "
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
36
|
+
"manualSummary": "xoziZG_manualSummary",
|
|
37
|
+
"headActions": "xoziZG_headActions",
|
|
38
|
+
"error": "xoziZG_error",
|
|
39
|
+
"area": "xoziZG_area",
|
|
40
|
+
"headText": "xoziZG_headText",
|
|
41
|
+
"danger": "xoziZG_danger",
|
|
42
|
+
"body": "xoziZG_body",
|
|
43
|
+
"name": "xoziZG_name",
|
|
44
|
+
"actions": "xoziZG_actions",
|
|
45
|
+
"statusLine": "xoziZG_statusLine",
|
|
46
|
+
"card": "xoziZG_card",
|
|
47
|
+
"check": "xoziZG_check",
|
|
48
|
+
"field": "xoziZG_field",
|
|
49
|
+
"sectionHead": "xoziZG_sectionHead",
|
|
50
|
+
"meta": "xoziZG_meta",
|
|
51
|
+
"choiceRow": "xoziZG_choiceRow",
|
|
52
|
+
"section": "xoziZG_section",
|
|
53
|
+
"desc": "xoziZG_desc",
|
|
54
|
+
"manual": "xoziZG_manual",
|
|
55
|
+
"header": "xoziZG_header",
|
|
56
|
+
"sectionLabel": "xoziZG_sectionLabel",
|
|
57
|
+
"choices": "xoziZG_choices",
|
|
58
|
+
"fieldLabel": "xoziZG_fieldLabel",
|
|
59
|
+
"hint": "xoziZG_hint",
|
|
60
|
+
"count": "xoziZG_count",
|
|
61
|
+
"link": "xoziZG_link"
|
|
47
62
|
};
|
|
48
63
|
//#endregion
|
|
49
64
|
//#region src/client/CodeArtsPluginCard.tsx
|
|
@@ -55,10 +70,9 @@ function formatStored(ts) {
|
|
|
55
70
|
return String(ts);
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
|
-
/**
|
|
59
|
-
const TRAILING_FREE = /\s*[((]\s*免费\s*[))]\s*$/;
|
|
73
|
+
/** 目录里的 name 自带「 · x0.18 · 福利」尾巴,这里已经用 Tag 表达了就别重复。 */
|
|
60
74
|
function modelName(choice) {
|
|
61
|
-
return choice.
|
|
75
|
+
return stripModelSuffixes(choice.name);
|
|
62
76
|
}
|
|
63
77
|
/** 官方用量明细页。 */
|
|
64
78
|
const OFFICIAL_USAGE_URL = "https://codearts.huaweicloud.com/portal/settings/personal-usage";
|
|
@@ -306,6 +320,8 @@ function CodeArtsPluginCard({ t }) {
|
|
|
306
320
|
const [savingModels, setSavingModels] = (0, react.useState)(false);
|
|
307
321
|
const [savedModels, setSavedModels] = (0, react.useState)(false);
|
|
308
322
|
const [modelError, setModelError] = (0, react.useState)(null);
|
|
323
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
324
|
+
const [refreshMsg, setRefreshMsg] = (0, react.useState)(null);
|
|
309
325
|
/** 账号身份是否展开:默认遮蔽(false = 显示 *******)。 */
|
|
310
326
|
const [accountsRevealed, setAccountsRevealed] = (0, react.useState)(false);
|
|
311
327
|
const [claiming, setClaiming] = (0, react.useState)(false);
|
|
@@ -409,6 +425,7 @@ function CodeArtsPluginCard({ t }) {
|
|
|
409
425
|
}
|
|
410
426
|
}
|
|
411
427
|
const all = selection?.choices.length ?? 0;
|
|
428
|
+
const blockedCount = selection?.choices.filter((c) => c.blocked === true).length ?? 0;
|
|
412
429
|
const wire = draft.length === all ? [] : draft;
|
|
413
430
|
const savedIds = selection?.choices.filter((c) => c.enabled).map((c) => c.id) ?? [];
|
|
414
431
|
const dirty = savedIds.length !== draft.length || savedIds.some((id) => !draft.includes(id));
|
|
@@ -443,6 +460,35 @@ function CodeArtsPluginCard({ t }) {
|
|
|
443
460
|
setSavingModels(false);
|
|
444
461
|
}
|
|
445
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* 手动重拉一次模型目录。
|
|
465
|
+
*
|
|
466
|
+
* 存在的理由:限时福利名单会轮换(模型被移出、被转正),后台每小时才拉一次,
|
|
467
|
+
* 干等或重启都不是办法。顺带清掉「福利今日不可用」标记 —— 活动回来时这是唯一
|
|
468
|
+
* 不用等第二天的出口。
|
|
469
|
+
*/
|
|
470
|
+
async function refreshCatalog() {
|
|
471
|
+
setRefreshing(true);
|
|
472
|
+
setRefreshMsg(null);
|
|
473
|
+
try {
|
|
474
|
+
const res = await fetch("/api/codearts/refresh-models", { method: "POST" });
|
|
475
|
+
const data = await res.json().catch(() => null);
|
|
476
|
+
if (!res.ok) throw new Error(data?.message ?? `HTTP ${res.status}`);
|
|
477
|
+
if (data?.selection !== void 0) {
|
|
478
|
+
setSelection(data.selection);
|
|
479
|
+
setDraft(data.selection.choices.filter((c) => c.enabled).map((c) => c.id));
|
|
480
|
+
setSavedModels(true);
|
|
481
|
+
}
|
|
482
|
+
setRefreshMsg(data?.refreshed === false ? t("refreshStale", { n: data.models ?? 0 }) : t("refreshed", {
|
|
483
|
+
n: data?.models ?? 0,
|
|
484
|
+
benefit: data?.benefit ?? 0
|
|
485
|
+
}));
|
|
486
|
+
} catch (err) {
|
|
487
|
+
setRefreshMsg(t("refreshFailed", { message: err instanceof Error ? err.message : String(err) }));
|
|
488
|
+
} finally {
|
|
489
|
+
setRefreshing(false);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
446
492
|
async function startOAuthLogin() {
|
|
447
493
|
setBusy(true);
|
|
448
494
|
setMsg(null);
|
|
@@ -721,35 +767,56 @@ function CodeArtsPluginCard({ t }) {
|
|
|
721
767
|
children: [
|
|
722
768
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
723
769
|
className: CodeArtsPluginCard_module_css_default.sectionHead,
|
|
724
|
-
children: [
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
770
|
+
children: [
|
|
771
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
772
|
+
className: CodeArtsPluginCard_module_css_default.sectionLabel,
|
|
773
|
+
children: t("modelsTitle")
|
|
774
|
+
}),
|
|
775
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
776
|
+
className: CodeArtsPluginCard_module_css_default.count,
|
|
777
|
+
children: t("selectedCount", {
|
|
778
|
+
n: draft.length,
|
|
779
|
+
total: all
|
|
780
|
+
})
|
|
781
|
+
}),
|
|
782
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
783
|
+
variant: "outline",
|
|
784
|
+
size: "sm",
|
|
785
|
+
disabled: refreshing,
|
|
786
|
+
onClick: () => void refreshCatalog(),
|
|
787
|
+
children: refreshing ? t("refreshing") : t("refreshModels")
|
|
732
788
|
})
|
|
733
|
-
|
|
789
|
+
]
|
|
734
790
|
}),
|
|
735
791
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
736
792
|
className: CodeArtsPluginCard_module_css_default.hint,
|
|
737
793
|
children: selection.restricted ? t("optionalModelsHint") : t("optionalModelsAllHint")
|
|
738
794
|
}),
|
|
795
|
+
blockedCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
796
|
+
className: CodeArtsPluginCard_module_css_default.hint,
|
|
797
|
+
children: t("benefitBlockedHint", { n: blockedCount })
|
|
798
|
+
}),
|
|
739
799
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
740
800
|
className: CodeArtsPluginCard_module_css_default.choices,
|
|
741
801
|
children: selection.choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
742
802
|
className: CodeArtsPluginCard_module_css_default.choiceRow,
|
|
743
|
-
children: [
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Checkbox, {
|
|
805
|
+
className: CodeArtsPluginCard_module_css_default.check,
|
|
806
|
+
checked: draft.includes(choice.id),
|
|
807
|
+
disabled: !selection.writable || savingModels,
|
|
808
|
+
onChange: (on) => setModel(choice.id, on),
|
|
809
|
+
label: modelName(choice)
|
|
810
|
+
}),
|
|
811
|
+
choice.benefit === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
812
|
+
tone: choice.blocked === true ? "warning" : "success",
|
|
813
|
+
children: choice.blocked === true ? t("benefitBlocked") : t("benefit")
|
|
814
|
+
}),
|
|
815
|
+
choice.ratio !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
816
|
+
tone: "quiet",
|
|
817
|
+
children: choice.ratio
|
|
818
|
+
})
|
|
819
|
+
]
|
|
753
820
|
}, choice.id))
|
|
754
821
|
}),
|
|
755
822
|
selection.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -924,7 +991,14 @@ const zh = {
|
|
|
924
991
|
optionalModelsAllHint: "所有模型默认都出现在选择器里;取消勾选不想用的即可。",
|
|
925
992
|
selectedCount: "已选 {n} / {total}",
|
|
926
993
|
selectAll: "全选",
|
|
927
|
-
|
|
994
|
+
benefit: "福利",
|
|
995
|
+
benefitBlocked: "福利今日不可用",
|
|
996
|
+
benefitBlockedHint: "{n} 个福利模型今天被上游判定没有权益(活动领完或渠道未开),发出去会直接报 benefit not found。",
|
|
997
|
+
refreshModels: "刷新模型列表",
|
|
998
|
+
refreshing: "刷新中…",
|
|
999
|
+
refreshed: "已刷新:{n} 个模型,其中 {benefit} 个走福利路由",
|
|
1000
|
+
refreshStale: "上游没回应,仍是旧名单({n} 个模型)",
|
|
1001
|
+
refreshFailed: "刷新失败:{message}",
|
|
928
1002
|
saveModels: "保存模型选择",
|
|
929
1003
|
saving: "保存中…",
|
|
930
1004
|
saved: "已保存",
|
|
@@ -1000,7 +1074,14 @@ const en = {
|
|
|
1000
1074
|
optionalModelsAllHint: "All models show in the picker by default; uncheck the ones you don’t want.",
|
|
1001
1075
|
selectedCount: "{n} of {total} selected",
|
|
1002
1076
|
selectAll: "Select all",
|
|
1003
|
-
|
|
1077
|
+
benefit: "Benefit",
|
|
1078
|
+
benefitBlocked: "Benefit unavailable today",
|
|
1079
|
+
benefitBlockedHint: "{n} benefit model(s) were told they have no entitlement today (campaign exhausted or channel not opened); sending to them fails with benefit not found.",
|
|
1080
|
+
refreshModels: "Refresh models",
|
|
1081
|
+
refreshing: "Refreshing…",
|
|
1082
|
+
refreshed: "Refreshed: {n} models, {benefit} on the benefit route",
|
|
1083
|
+
refreshStale: "Upstream did not answer; still showing the old {n} models",
|
|
1084
|
+
refreshFailed: "Refresh failed: {message}",
|
|
1004
1085
|
saveModels: "Save selection",
|
|
1005
1086
|
saving: "Saving…",
|
|
1006
1087
|
saved: "Saved",
|
package/lib/index.d.mts
CHANGED
|
@@ -7,18 +7,18 @@ import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
|
|
|
7
7
|
/**
|
|
8
8
|
* CodeArts model catalog.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* **目录只放上游给的东西。** 唯一来源是动态发现(`src/discovery.ts` +
|
|
11
|
+
* `upstream.fetchModels`):启动、每小时、以及卡片上「刷新模型列表」各拉一次,
|
|
12
|
+
* 取内置 / 限时福利 / agent-center 三路的并集。上游没返回的模型就是没有,
|
|
13
|
+
* 未登录或三路全失败时目录是空的 —— 不拿一份历史名单冒充「可用模型」。
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
15
|
+
* `FALLBACK_CODEARTS_MODELS` 因此**不再往目录里塞模型**,只剩一个按 id 的参数表:
|
|
16
|
+
* 上游有时不给 `contextWindow` / `maxTokens`,而写错的 `maxTokens` 会让每个请求
|
|
17
|
+
* 直接失败(见 `MEASURED_MAX_TOKENS`)。它只给发现到的模型补参数,别的什么都不管。
|
|
18
|
+
*
|
|
19
|
+
* 福利路由同理:只有 `gateway/config` 亲口给出的模型才算福利(见 `isBenefitModel`)。
|
|
20
|
+
* 这条渠道 2026-10-21 起整体不可用,于是福利模型当天就从目录里消失 —— 那是事实,
|
|
21
|
+
* 不是故障,硬撑只会把能正常用的积分模型推到一条不存在的渠道上。
|
|
22
22
|
*
|
|
23
23
|
* @module dsh-codearts/catalog
|
|
24
24
|
*/
|
|
@@ -29,6 +29,12 @@ interface CodeArtsModelInfo {
|
|
|
29
29
|
contextWindow: number;
|
|
30
30
|
maxTokens: number;
|
|
31
31
|
supportsImages: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* 积分消耗倍率(上游 `credit[].ratio_display` 的原值,如 `0.18x`)。
|
|
34
|
+
*
|
|
35
|
+
* 来自模型发现;福利模型由展示层强制成 `x0.00`,不依赖上游是否给 credit。
|
|
36
|
+
*/
|
|
37
|
+
ratio?: string;
|
|
32
38
|
/**
|
|
33
39
|
* 该模型走限时福利(免费套餐)路由。
|
|
34
40
|
*
|
|
@@ -38,12 +44,11 @@ interface CodeArtsModelInfo {
|
|
|
38
44
|
benefit?: boolean;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* Updated 2026-09 from codearts2api's known model set.
|
|
47
|
+
* 参数表(不是模型名单):只给**发现到的**模型补 `contextWindow` / `maxTokens` /
|
|
48
|
+
* `supportsImages`,绝不会因为这里有条目就把模型塞进目录。
|
|
44
49
|
*
|
|
45
50
|
* maxTokens 注记(2026-09-30 实测,scripts/param-probe.mjs 直连上游二分验证):
|
|
46
|
-
*
|
|
51
|
+
* 福利 DeepSeek 模型的 max_tokens 服务端硬上限是 **65536**,更大的值
|
|
47
52
|
* (73728 / 98304 / 131072 / 384000 / 393216)一律流内报
|
|
48
53
|
* InferHub.001001005.400 "The request param is invalid, Please check it"。
|
|
49
54
|
* 此前的 384000/393216 是占位值:DSH 会把 catalog 的 maxTokens 作为
|
|
@@ -52,6 +57,7 @@ interface CodeArtsModelInfo {
|
|
|
52
57
|
declare const FALLBACK_CODEARTS_MODELS: readonly CodeArtsModelInfo[];
|
|
53
58
|
/** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
|
|
54
59
|
declare class CodeArtsCatalog {
|
|
60
|
+
/** 空目录起步:没发现过就是没有模型,不拿历史名单冒充。 */
|
|
55
61
|
private models;
|
|
56
62
|
current(): readonly CodeArtsModelInfo[];
|
|
57
63
|
set(models: readonly CodeArtsModelInfo[]): void;
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as isBenefitModel, C as upstreamErrorMessage, D as creditRatioLabel, E as clearBenefitBlocked, F as startLogin, M as CodeArtsCredentialStore, N as codeartsAccountsPath, O as filterEnabledModels, P as codeartsAuthPath, S as signedGet, T as FALLBACK_CODEARTS_MODELS, _ as isWelfareClaimedStatus, a as SNAP_HOST, b as prepareChatBody, c as claimDailyWelfare, d as fetchModels, f as fetchWelfareCampaigns, g as isTpmLimit, h as isQueueableRate, i as refreshCatalog, j as CodeArtsAccountStore, k as isBenefitBlocked, l as degradeTokenCeiling, m as isParamInvalidFrame, n as codeartsUsagePath, o as canonicalModel, p as isConcurrencyCap, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as fetchCallerIdentity, v as maxTokensCeilingFor, w as CodeArtsCatalog, x as rateLimitFrame, y as pickDailyWelfareCampaign } from "./usage-Djtou78p.mjs";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { createServer } from "node:http";
|
|
@@ -380,7 +380,8 @@ var CodeArtsCreditCache = class {
|
|
|
380
380
|
* models and points pi-ai at the shim's OpenAI-compatible endpoint.
|
|
381
381
|
*
|
|
382
382
|
* MVP simplifications vs dsh-codebuddy-cli:
|
|
383
|
-
* -
|
|
383
|
+
* - only the default-slot credit multiplier goes into the model name (`(x0.18)`);
|
|
384
|
+
* per-request cost is not modelled (subscription quota, cost reported as zero)
|
|
384
385
|
* - no per-model reasoning ladder (models are surfaced as plain chat models)
|
|
385
386
|
*
|
|
386
387
|
* @module dsh-codearts/adapter
|
|
@@ -655,21 +656,6 @@ const SERVER_COOLDOWN_MS = 3e4;
|
|
|
655
656
|
/** 零散错误累计到阈值后冷却该账号。 */
|
|
656
657
|
const ERROR_COOLDOWN_MS = 6e4;
|
|
657
658
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
658
|
-
/**
|
|
659
|
-
* 在**同一个账号**上重发,直到不再撞上限或预算耗尽。
|
|
660
|
-
*
|
|
661
|
-
* 只在没有别的账号可选时使用 —— 等 52s 是最后手段,能换号就换号。
|
|
662
|
-
* 命中上限不甩给 pi-ai:429 会被重试 5 次,每次再去要一个会话,越试越满。
|
|
663
|
-
*/
|
|
664
|
-
async function openStream(credential, prepared, signal) {
|
|
665
|
-
const until = Date.now() + CAP_WAIT_LIMIT;
|
|
666
|
-
for (;;) {
|
|
667
|
-
const result = await chatStream(credential, prepared, signal);
|
|
668
|
-
if (result.ok) return result;
|
|
669
|
-
if (!(isConcurrencyCap(result) && !signal.aborted && Date.now() + CAP_RETRY_GAP <= until)) return result;
|
|
670
|
-
await sleep(CAP_RETRY_GAP);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
659
|
function createCodeArtsShim(options) {
|
|
674
660
|
const { store, catalog } = options;
|
|
675
661
|
const logger = options.logger;
|
|
@@ -775,6 +761,21 @@ function createCodeArtsShim(options) {
|
|
|
775
761
|
const reader = result.stream.getReader();
|
|
776
762
|
const first = await reader.read();
|
|
777
763
|
const head = first.done ? void 0 : first.value;
|
|
764
|
+
const rate = head === void 0 ? void 0 : rateLimitFrame(new TextDecoder().decode(head));
|
|
765
|
+
if (rate !== void 0) {
|
|
766
|
+
reader.releaseLock();
|
|
767
|
+
await result.stream.cancel().catch(() => {});
|
|
768
|
+
logger?.warn(`dsh-codearts: ${prepared.model} 首帧被判限流(${rate.code}),进入本地排队`);
|
|
769
|
+
return {
|
|
770
|
+
prepared,
|
|
771
|
+
result: {
|
|
772
|
+
ok: false,
|
|
773
|
+
kind: "soft_rate",
|
|
774
|
+
status: 429,
|
|
775
|
+
message: `${rate.code}: ${rate.message}`
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
778
779
|
const shouldHeal = heal < MAX_CEILING_HEALS && head !== void 0 && isParamInvalidFrame(new TextDecoder().decode(head));
|
|
779
780
|
reader.releaseLock();
|
|
780
781
|
if (!shouldHeal) return {
|
|
@@ -795,6 +796,39 @@ function createCodeArtsShim(options) {
|
|
|
795
796
|
logger?.warn(`dsh-codearts: ${prepared.model} 被判参数非法,max_tokens 降档到 ${maxTokensCeilingFor(prepared.model)} 后重发`);
|
|
796
797
|
}
|
|
797
798
|
}
|
|
799
|
+
/**
|
|
800
|
+
* 在**同一个账号**上重发,直到不再被限流或预算耗尽。
|
|
801
|
+
*
|
|
802
|
+
* 并发槽位是账号级的:有别的号就该换号,等 52s 是最后手段。
|
|
803
|
+
* TPM 是模型级配额:换号只是换个账号撞同一堵墙,只能原地等窗口滑过去 ——
|
|
804
|
+
* 间隔逐次翻倍(3s → 6s → 12s → 24s,封顶 30s),避免在配额已经满的时候
|
|
805
|
+
* 把重试本身也打成一波流量。
|
|
806
|
+
*
|
|
807
|
+
* 命中限流不甩给 pi-ai:429 会被重试 5 次,每次再撞一次限流,越试越满。
|
|
808
|
+
*/
|
|
809
|
+
async function openStream(credential, raw, contextWindow, signal) {
|
|
810
|
+
const started = Date.now();
|
|
811
|
+
const until = started + CAP_WAIT_LIMIT;
|
|
812
|
+
let gap = CAP_RETRY_GAP;
|
|
813
|
+
for (;;) {
|
|
814
|
+
const opened = await openWithCeilingHeal(credential, raw, contextWindow, signal);
|
|
815
|
+
const waitedMs = Date.now() - started;
|
|
816
|
+
if (opened.result.ok || signal.aborted) return {
|
|
817
|
+
...opened,
|
|
818
|
+
waitedMs
|
|
819
|
+
};
|
|
820
|
+
if (!isQueueableRate(opened.result)) return {
|
|
821
|
+
...opened,
|
|
822
|
+
waitedMs
|
|
823
|
+
};
|
|
824
|
+
if (isTpmLimit(opened.result)) gap = Math.min(gap * 2, 3e4);
|
|
825
|
+
if (Date.now() + gap > until) return {
|
|
826
|
+
...opened,
|
|
827
|
+
waitedMs
|
|
828
|
+
};
|
|
829
|
+
await sleep(gap);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
798
832
|
/** 先交回降档判定读过的那一帧(它是流的开头,丢了就少一段),再接着读剩下的。 */
|
|
799
833
|
async function* drainFrames(reader, head) {
|
|
800
834
|
if (head !== void 0) yield head;
|
|
@@ -838,6 +872,8 @@ function createCodeArtsShim(options) {
|
|
|
838
872
|
pool.sync(ids);
|
|
839
873
|
const tried = /* @__PURE__ */ new Set();
|
|
840
874
|
let cappedAccount;
|
|
875
|
+
let tpmAccount;
|
|
876
|
+
let queueWaitedMs = 0;
|
|
841
877
|
let lastKind;
|
|
842
878
|
let lastStatus = 0;
|
|
843
879
|
let lastMessage = "";
|
|
@@ -874,6 +910,10 @@ function createCodeArtsShim(options) {
|
|
|
874
910
|
cappedAccount = id;
|
|
875
911
|
continue;
|
|
876
912
|
}
|
|
913
|
+
if (isTpmLimit(result)) {
|
|
914
|
+
tpmAccount = id;
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
877
917
|
if (result.kind === "session_dead") {
|
|
878
918
|
pool.disable(id, result.message);
|
|
879
919
|
continue;
|
|
@@ -889,39 +929,41 @@ function createCodeArtsShim(options) {
|
|
|
889
929
|
}
|
|
890
930
|
}
|
|
891
931
|
if (controller.signal.aborted) return;
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
if (result.ok) {
|
|
904
|
-
pool.noteSuccess(cappedAccount);
|
|
905
|
-
await pipeToResponse(res, result.stream, controller.signal, {
|
|
906
|
-
accountId: cappedAccount,
|
|
907
|
-
model: prepared.model
|
|
908
|
-
});
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
lastKind = result.kind;
|
|
912
|
-
lastStatus = result.status;
|
|
913
|
-
lastMessage = result.message;
|
|
932
|
+
const queueAccount = cappedAccount ?? tpmAccount;
|
|
933
|
+
if (queueAccount !== void 0) {
|
|
934
|
+
if (pool.acquire(queueAccount)) try {
|
|
935
|
+
const credential = await store.resolve(queueAccount);
|
|
936
|
+
if (credential !== void 0) {
|
|
937
|
+
const opened = await openStream(credential, raw, contextWindow, controller.signal);
|
|
938
|
+
const { result } = opened;
|
|
939
|
+
queueWaitedMs = opened.waitedMs;
|
|
940
|
+
if (controller.signal.aborted) {
|
|
941
|
+
if (result.ok) await result.stream.cancel().catch(() => {});
|
|
942
|
+
return;
|
|
914
943
|
}
|
|
915
|
-
|
|
916
|
-
|
|
944
|
+
if (result.ok) {
|
|
945
|
+
pool.noteSuccess(queueAccount);
|
|
946
|
+
await pipeToResponse(res, result.stream, controller.signal, {
|
|
947
|
+
accountId: queueAccount,
|
|
948
|
+
model: opened.prepared.model
|
|
949
|
+
}, opened.head);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
lastKind = result.kind;
|
|
953
|
+
lastStatus = result.status;
|
|
954
|
+
lastMessage = result.message;
|
|
917
955
|
}
|
|
956
|
+
} finally {
|
|
957
|
+
pool.release(queueAccount);
|
|
918
958
|
}
|
|
919
959
|
}
|
|
920
960
|
if (lastKind === void 0) {
|
|
921
961
|
writeOpenAIError(res, 429, "soft_rate", "codearts 当前没有可用账号(全部被禁用或冷却中),请稍后重试");
|
|
922
962
|
return;
|
|
923
963
|
}
|
|
924
|
-
|
|
964
|
+
const explanation = upstreamErrorMessage(lastMessage, lastStatus);
|
|
965
|
+
const waited = queueWaitedMs >= 1e3 ? `(本地已排队重试 ${Math.round(queueWaitedMs / 1e3)} 秒)` : "";
|
|
966
|
+
writeOpenAIError(res, KIND_STATUS[lastKind], lastKind, `${explanation}${waited}`);
|
|
925
967
|
}
|
|
926
968
|
/**
|
|
927
969
|
* 记下一帧里的上游流内错误。判据宽松(帧文本含 "error" 才解析),解析不出来就忽略 ——
|
|
@@ -1014,11 +1056,13 @@ const CODEARTS_LOGIN_STATUS_PATH = "/api/codearts/login/status";
|
|
|
1014
1056
|
const CODEARTS_ACCOUNTS_PATH = "/api/codearts/accounts";
|
|
1015
1057
|
/** POST /api/codearts/claim — 领一次福利中心的每日签到积分(幂等由上游按天去重)。 */
|
|
1016
1058
|
const CODEARTS_CLAIM_PATH = "/api/codearts/claim";
|
|
1059
|
+
/** POST /api/codearts/refresh-models — 立刻重拉一次模型目录,并清掉「福利今日不可用」标记。 */
|
|
1060
|
+
const CODEARTS_REFRESH_MODELS_PATH = "/api/codearts/refresh-models";
|
|
1017
1061
|
/**
|
|
1018
1062
|
* 由完整目录 + 已存 enabled 计算 selection。
|
|
1019
1063
|
* - enabled 为空/未存 => 全部勾选(restricted=false)
|
|
1020
1064
|
* - enabled 为非空列表 => 仅勾选其中的(restricted=true),缺的按未勾选
|
|
1021
|
-
* 福利模型用 catalog 的 benefit 标记(这里靠 model.
|
|
1065
|
+
* 福利模型用 catalog 的 benefit 标记(这里靠 model.benefit 字段,由调用方注入)。
|
|
1022
1066
|
*/
|
|
1023
1067
|
function selectionOf(models, enabled, writable) {
|
|
1024
1068
|
const allowed = new Set(enabled ?? []);
|
|
@@ -1027,13 +1071,29 @@ function selectionOf(models, enabled, writable) {
|
|
|
1027
1071
|
choices: models.map((m) => ({
|
|
1028
1072
|
id: m.id,
|
|
1029
1073
|
name: m.name,
|
|
1030
|
-
|
|
1074
|
+
benefit: m.benefit,
|
|
1075
|
+
blocked: m.blocked,
|
|
1076
|
+
ratio: m.ratio,
|
|
1031
1077
|
enabled: restricted ? allowed.has(m.id) : true
|
|
1032
1078
|
})),
|
|
1033
1079
|
restricted,
|
|
1034
1080
|
writable
|
|
1035
1081
|
};
|
|
1036
1082
|
}
|
|
1083
|
+
/** 目录 -> 勾选 UI 的选项(福利标记、今日不可用标记与倍率都在这里算)。 */
|
|
1084
|
+
function choicesOf(deps) {
|
|
1085
|
+
return deps.models().map((m) => {
|
|
1086
|
+
const benefit = isBenefitModel(m.id);
|
|
1087
|
+
const ratio = creditRatioLabel(m.ratio, benefit);
|
|
1088
|
+
return {
|
|
1089
|
+
id: m.id,
|
|
1090
|
+
name: m.name,
|
|
1091
|
+
benefit,
|
|
1092
|
+
blocked: isBenefitBlocked(m.id),
|
|
1093
|
+
...ratio === void 0 ? {} : { ratio }
|
|
1094
|
+
};
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1037
1097
|
function json(res, status, body) {
|
|
1038
1098
|
const payload = JSON.stringify(body);
|
|
1039
1099
|
res.writeHead(status, {
|
|
@@ -1225,11 +1285,7 @@ function modelsSelectionHandler(deps) {
|
|
|
1225
1285
|
return;
|
|
1226
1286
|
}
|
|
1227
1287
|
if (req.method === "GET") {
|
|
1228
|
-
const all = deps
|
|
1229
|
-
id: m.id,
|
|
1230
|
-
name: m.name,
|
|
1231
|
-
free: isBenefitModel(m.id)
|
|
1232
|
-
}));
|
|
1288
|
+
const all = choicesOf(deps);
|
|
1233
1289
|
const writable = deps.setEnabledModels !== void 0;
|
|
1234
1290
|
json(res, 200, { selection: selectionOf(all, deps.enabledModels?.(), writable) });
|
|
1235
1291
|
return;
|
|
@@ -1254,11 +1310,7 @@ function modelsSelectionHandler(deps) {
|
|
|
1254
1310
|
json(res, 403, { error: "selection-not-writable" });
|
|
1255
1311
|
return;
|
|
1256
1312
|
}
|
|
1257
|
-
json(res, 200, { selection: selectionOf(deps
|
|
1258
|
-
id: m.id,
|
|
1259
|
-
name: m.name,
|
|
1260
|
-
free: isBenefitModel(m.id)
|
|
1261
|
-
})), deps.enabledModels?.(), true) });
|
|
1313
|
+
json(res, 200, { selection: selectionOf(choicesOf(deps), deps.enabledModels?.(), true) });
|
|
1262
1314
|
} catch (error) {
|
|
1263
1315
|
json(res, 500, { error: safeMessage(error) });
|
|
1264
1316
|
}
|
|
@@ -1375,6 +1427,43 @@ function claimHandler(deps) {
|
|
|
1375
1427
|
}
|
|
1376
1428
|
};
|
|
1377
1429
|
}
|
|
1430
|
+
/**
|
|
1431
|
+
* POST /api/codearts/refresh-models — 立刻重拉目录,并清掉「福利今日不可用」标记。
|
|
1432
|
+
*
|
|
1433
|
+
* 清标记放在拉目录**之前**:福利回来时这是唯一的自救出口,不能让用户等到明天。
|
|
1434
|
+
* 代价是「今天确实领完了」的那种缺失也会被一并清掉,再点一次就重新记上。
|
|
1435
|
+
*/
|
|
1436
|
+
function refreshModelsHandler(deps) {
|
|
1437
|
+
return async (req, res) => {
|
|
1438
|
+
if (req.method !== "POST") {
|
|
1439
|
+
json(res, 405, { error: "method not allowed" });
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (!loopbackRequest(req)) {
|
|
1443
|
+
json(res, 403, { error: "request-not-trusted" });
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (deps.refreshModels === void 0) {
|
|
1447
|
+
json(res, 404, { error: "refresh not available" });
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
try {
|
|
1451
|
+
clearBenefitBlocked();
|
|
1452
|
+
const counted = await deps.refreshModels();
|
|
1453
|
+
const selection = selectionOf(choicesOf(deps), deps.enabledModels?.(), deps.setEnabledModels !== void 0);
|
|
1454
|
+
json(res, 200, {
|
|
1455
|
+
ok: true,
|
|
1456
|
+
...counted,
|
|
1457
|
+
selection
|
|
1458
|
+
});
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
json(res, 500, {
|
|
1461
|
+
ok: false,
|
|
1462
|
+
message: safeMessage(error)
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1378
1467
|
/** Mount the status / models / token routes. */
|
|
1379
1468
|
function registerCodeArtsStatusRoute(ctx, deps) {
|
|
1380
1469
|
ctx.effect(() => {
|
|
@@ -1421,6 +1510,11 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
1421
1510
|
path: CODEARTS_CLAIM_PATH,
|
|
1422
1511
|
handler: claimHandler(deps)
|
|
1423
1512
|
});
|
|
1513
|
+
const disposeRefresh = ctx.webServer.register({
|
|
1514
|
+
kind: "exact",
|
|
1515
|
+
path: CODEARTS_REFRESH_MODELS_PATH,
|
|
1516
|
+
handler: refreshModelsHandler(deps)
|
|
1517
|
+
});
|
|
1424
1518
|
return () => {
|
|
1425
1519
|
disposeStatus();
|
|
1426
1520
|
disposeModels();
|
|
@@ -1430,6 +1524,7 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
1430
1524
|
disposeLoginStatus();
|
|
1431
1525
|
disposeAccounts();
|
|
1432
1526
|
disposeClaim();
|
|
1527
|
+
disposeRefresh();
|
|
1433
1528
|
for (const s of activeLogins.values()) s.close();
|
|
1434
1529
|
activeLogins.clear();
|
|
1435
1530
|
};
|
|
@@ -1650,6 +1745,8 @@ function apply(ctx, rawConfig) {
|
|
|
1650
1745
|
await settings.update(settingsCompatNamespace(ctx), { enabledModels: [...ids] });
|
|
1651
1746
|
return true;
|
|
1652
1747
|
};
|
|
1748
|
+
/** 手动「刷新模型列表」的预算:三路来源各打一次,比每小时的后台刷新更该给足时间。 */
|
|
1749
|
+
const MANUAL_REFRESH_MS = 6e4;
|
|
1653
1750
|
ctx.inject(["webServer"], (webCtx) => registerCodeArtsStatusRoute(webCtx, {
|
|
1654
1751
|
store,
|
|
1655
1752
|
models: () => catalog.current(),
|
|
@@ -1692,6 +1789,15 @@ function apply(ctx, rawConfig) {
|
|
|
1692
1789
|
message: error instanceof Error ? error.message : String(error)
|
|
1693
1790
|
};
|
|
1694
1791
|
}
|
|
1792
|
+
},
|
|
1793
|
+
refreshModels: async () => {
|
|
1794
|
+
const outcome = await refreshCatalog(store, catalog, AbortSignal.timeout(MANUAL_REFRESH_MS));
|
|
1795
|
+
const models = catalog.current();
|
|
1796
|
+
return {
|
|
1797
|
+
models: models.length,
|
|
1798
|
+
benefit: models.filter((model) => isBenefitModel(model.id)).length,
|
|
1799
|
+
refreshed: outcome?.anySucceeded === true
|
|
1800
|
+
};
|
|
1695
1801
|
}
|
|
1696
1802
|
}));
|
|
1697
1803
|
installSettingsSectionCompat(ctx, CODEARTS_SETTINGS_NS, Config, config, {
|
|
@@ -830,12 +830,11 @@ var CodeArtsAccountStore = class {
|
|
|
830
830
|
//#endregion
|
|
831
831
|
//#region src/catalog.ts
|
|
832
832
|
/**
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
* Updated 2026-09 from codearts2api's known model set.
|
|
833
|
+
* 参数表(不是模型名单):只给**发现到的**模型补 `contextWindow` / `maxTokens` /
|
|
834
|
+
* `supportsImages`,绝不会因为这里有条目就把模型塞进目录。
|
|
836
835
|
*
|
|
837
836
|
* maxTokens 注记(2026-09-30 实测,scripts/param-probe.mjs 直连上游二分验证):
|
|
838
|
-
*
|
|
837
|
+
* 福利 DeepSeek 模型的 max_tokens 服务端硬上限是 **65536**,更大的值
|
|
839
838
|
* (73728 / 98304 / 131072 / 384000 / 393216)一律流内报
|
|
840
839
|
* InferHub.001001005.400 "The request param is invalid, Please check it"。
|
|
841
840
|
* 此前的 384000/393216 是占位值:DSH 会把 catalog 的 maxTokens 作为
|
|
@@ -900,36 +899,73 @@ const FALLBACK_CODEARTS_MODELS = [
|
|
|
900
899
|
},
|
|
901
900
|
{
|
|
902
901
|
id: "deepseek-v4-flash-0731",
|
|
903
|
-
name: "DeepSeek-V4-Flash (
|
|
902
|
+
name: "DeepSeek-V4-Flash (福利)",
|
|
904
903
|
contextWindow: 131072,
|
|
905
904
|
maxTokens: 65536,
|
|
906
905
|
supportsImages: true
|
|
907
906
|
},
|
|
908
907
|
{
|
|
909
908
|
id: "deepseek-v4-pro-0813",
|
|
910
|
-
name: "DeepSeek-V4-Pro (
|
|
909
|
+
name: "DeepSeek-V4-Pro (福利)",
|
|
911
910
|
contextWindow: 131072,
|
|
912
911
|
maxTokens: 65536,
|
|
913
912
|
supportsImages: true
|
|
914
913
|
},
|
|
915
914
|
{
|
|
916
915
|
id: "deepseek-v4.1-flash",
|
|
917
|
-
name: "DeepSeek-V4.1-Flash
|
|
916
|
+
name: "DeepSeek-V4.1-Flash",
|
|
918
917
|
contextWindow: 131072,
|
|
919
918
|
maxTokens: 65536,
|
|
920
919
|
supportsImages: true
|
|
921
920
|
},
|
|
922
921
|
{
|
|
923
922
|
id: "glm-5.3-flash",
|
|
924
|
-
name: "GLM-5.3-Flash (
|
|
923
|
+
name: "GLM-5.3-Flash (福利)",
|
|
925
924
|
contextWindow: 128e3,
|
|
926
925
|
maxTokens: 8e3,
|
|
927
926
|
supportsImages: true
|
|
928
927
|
}
|
|
929
928
|
];
|
|
929
|
+
/**
|
|
930
|
+
* 倍率的展示写法:福利恒为 `x0.00`,其余用上游的数值,拿不到就不显示。
|
|
931
|
+
*
|
|
932
|
+
* 福利模型不走积分,倍率显示成 0 比显示上游某个残留 credit 更接近事实。
|
|
933
|
+
*/
|
|
934
|
+
function creditRatioLabel(ratio, benefit) {
|
|
935
|
+
if (benefit) return "x0.00";
|
|
936
|
+
const value = (ratio ?? "").trim().replace(/x$/i, "").trim();
|
|
937
|
+
return /^\d+(?:\.\d+)?$/.test(value) ? `x${Number(value).toFixed(2)}` : void 0;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* 模型在宿主下拉里的**名字**带一个标记尾巴:` · x0.18`、` · x0.00 · 福利`。
|
|
941
|
+
*
|
|
942
|
+
* 写法照搬 dsh-codebuddy-cli 的 provider:倍率在前、上游徽章措辞在后,统一用
|
|
943
|
+
* ` · ` 连接(`Name · x0.71 · 限时免费`)。宿主自己的模型下拉只渲染文字,
|
|
944
|
+
* 插不进 Tag,所以那是用户唯一能看见标记的地方;插件卡片会反过来把尾巴剥掉、
|
|
945
|
+
* 改挂 Tag(见 client 的 modelName),两边都不会重复。
|
|
946
|
+
*/
|
|
947
|
+
function modelDisplayName(name, benefit, ratio) {
|
|
948
|
+
const base = stripModelSuffixes(name);
|
|
949
|
+
const parts = [creditRatioLabel(ratio, benefit), benefit ? "福利" : void 0].filter((part) => part !== void 0);
|
|
950
|
+
return parts.length === 0 ? base : `${base} · ${parts.join(" · ")}`;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* 剥掉模型名尾巴上的标记:` · x0.18`、` · x0.00 · 福利`、`(福利)`、`(免费)`、
|
|
954
|
+
* `(x0.18)` 等(兼容旧格式)。
|
|
955
|
+
*/
|
|
956
|
+
function stripModelSuffixes(name) {
|
|
957
|
+
let base = name;
|
|
958
|
+
for (;;) {
|
|
959
|
+
const next = base.replace(/\s*·\s*(?:x\d+(?:\.\d+)?|(?:限时)?(?:免费|福利)(?:\s+x\d+(?:\.\d+)?)?)\s*$/, "");
|
|
960
|
+
if (next === base) break;
|
|
961
|
+
base = next;
|
|
962
|
+
}
|
|
963
|
+
return base.replace(/\s*[((]\s*(?:(?:免费|福利)(?:\s+x\d+(?:\.\d+)?)?|x\d+(?:\.\d+)?)\s*[))]\s*$/, "").trim();
|
|
964
|
+
}
|
|
930
965
|
/** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
|
|
931
966
|
var CodeArtsCatalog = class {
|
|
932
|
-
|
|
967
|
+
/** 空目录起步:没发现过就是没有模型,不拿历史名单冒充。 */
|
|
968
|
+
models = [];
|
|
933
969
|
current() {
|
|
934
970
|
return this.models;
|
|
935
971
|
}
|
|
@@ -947,19 +983,15 @@ function filterEnabledModels(models, enabled) {
|
|
|
947
983
|
const kept = models.filter((model) => allow.has(model.id));
|
|
948
984
|
return kept.length === 0 ? models : kept;
|
|
949
985
|
}
|
|
950
|
-
const benefitLower = new Set([
|
|
951
|
-
"deepseek-v4-flash-0731",
|
|
952
|
-
"deepseek-v4-pro-0813",
|
|
953
|
-
"deepseek-v4.1-flash",
|
|
954
|
-
"glm-5.3-flash"
|
|
955
|
-
].map((m) => m.toLowerCase()));
|
|
956
986
|
/**
|
|
957
|
-
* 动态发现确认的福利模型(小写 id
|
|
987
|
+
* 动态发现确认的福利模型(小写 id)—— 这是「是不是福利」的**唯一**依据。
|
|
988
|
+
*
|
|
989
|
+
* 只认 `gateway/config` 给过的名单,没有静态兜底:猜错一次的代价不对称 ——
|
|
990
|
+
* 漏带 `maas_type: benefit` 是福利模型报 404(它本来就不可用),多带一次却是把
|
|
991
|
+
* 一个正常能用的积分模型推进一条不存在的渠道,直接 benefit not found。
|
|
958
992
|
*
|
|
959
993
|
* 与 codearts2api 的差别:那边按账号隔离目录,本插件的聊天链路(shim → upstream)
|
|
960
|
-
*
|
|
961
|
-
* `maas_type: benefit` 头,也不要漏带导致 404。套餐轮换后模型「转正」时,
|
|
962
|
-
* 下次发现会把它从这份集合里去掉。
|
|
994
|
+
* 拿不到账号上下文,所以这里取所有成功账号的并集。
|
|
963
995
|
*/
|
|
964
996
|
const discoveredBenefit = /* @__PURE__ */ new Set();
|
|
965
997
|
/** 用一次发现的结果替换动态福利集合(传空数组表示「本轮确认没有福利模型」)。 */
|
|
@@ -973,15 +1005,45 @@ function setDiscoveredBenefitModels(ids) {
|
|
|
973
1005
|
/**
|
|
974
1006
|
* 该模型是否走限时福利路由(按小写 ID 判定)。
|
|
975
1007
|
*
|
|
976
|
-
*
|
|
977
|
-
* 因为发现成功时会把它从 `discoveredBenefit` 里剔除,而种子只在该集合为空时兜底。
|
|
1008
|
+
* 只认发现结果:`gateway/config` 没给过的模型一律不算福利,没有静态兜底。
|
|
978
1009
|
*/
|
|
979
1010
|
function isBenefitModel(id) {
|
|
1011
|
+
return discoveredBenefit.has(id.trim().toLowerCase());
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 今天被上游判定「没有福利权益」的模型(小写 id -> 本地日期键)。
|
|
1015
|
+
*
|
|
1016
|
+
* 判据只有一个:聊天回 `InferHub.4004.200 benefit not found`。上游没有任何接口能
|
|
1017
|
+
* 提前告诉我们福利余量(`statistics/plugin` 的 `model_quota_num` 实测恒为 -1,
|
|
1018
|
+
* 唯一能给数的 opengw balance 也不覆盖福利池),所以只能「失败一次才知道」。
|
|
1019
|
+
*
|
|
1020
|
+
* 按本地日期存:福利是每天发的,昨天的失败不代表今天。卡片据此把模型标成
|
|
1021
|
+
* 「福利今日不可用」,但**不**替用户取消勾选 —— 那是改他的设置。
|
|
1022
|
+
*/
|
|
1023
|
+
const benefitBlocked = /* @__PURE__ */ new Map();
|
|
1024
|
+
function localDayKey(at = Date.now()) {
|
|
1025
|
+
const d = new Date(at);
|
|
1026
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
1027
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
1028
|
+
return `${d.getFullYear()}-${month}-${day}`;
|
|
1029
|
+
}
|
|
1030
|
+
/** 记一次「这个模型今天没有福利权益」。 */
|
|
1031
|
+
function markBenefitBlocked(id) {
|
|
1032
|
+
const key = id.trim().toLowerCase();
|
|
1033
|
+
if (key !== "") benefitBlocked.set(key, localDayKey());
|
|
1034
|
+
}
|
|
1035
|
+
/** 该模型今天是否已经因为福利权益缺失失败过。 */
|
|
1036
|
+
function isBenefitBlocked(id) {
|
|
980
1037
|
const key = id.trim().toLowerCase();
|
|
981
|
-
|
|
982
|
-
if (
|
|
983
|
-
if (
|
|
984
|
-
|
|
1038
|
+
const day = benefitBlocked.get(key);
|
|
1039
|
+
if (day === void 0) return false;
|
|
1040
|
+
if (day === localDayKey()) return true;
|
|
1041
|
+
benefitBlocked.delete(key);
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
/** 清空福利缺失记录 —— 手动刷新模型列表时调,给用户一个自救出口。 */
|
|
1045
|
+
function clearBenefitBlocked() {
|
|
1046
|
+
benefitBlocked.clear();
|
|
985
1047
|
}
|
|
986
1048
|
/**
|
|
987
1049
|
* 合并多路来源的模型清单(内置 / 限时福利 / agent-center)。
|
|
@@ -1016,12 +1078,14 @@ function richerModel(current, next) {
|
|
|
1016
1078
|
const benefit = current.benefit === true || next.benefit === true;
|
|
1017
1079
|
const keep = richerThan(next, current) ? next : current;
|
|
1018
1080
|
const drop = keep === next ? current : next;
|
|
1081
|
+
const ratio = keep.ratio ?? drop.ratio;
|
|
1019
1082
|
return {
|
|
1020
1083
|
id: keep.id,
|
|
1021
1084
|
name: keep.name || drop.name || keep.id,
|
|
1022
1085
|
contextWindow: keep.contextWindow > 0 ? keep.contextWindow : drop.contextWindow,
|
|
1023
1086
|
maxTokens: keep.maxTokens > 0 ? keep.maxTokens : drop.maxTokens,
|
|
1024
1087
|
supportsImages: keep.supportsImages || drop.supportsImages,
|
|
1088
|
+
...ratio === void 0 ? {} : { ratio },
|
|
1025
1089
|
...benefit ? { benefit: true } : {}
|
|
1026
1090
|
};
|
|
1027
1091
|
}
|
|
@@ -1032,29 +1096,26 @@ function richerThan(a, b) {
|
|
|
1032
1096
|
return a.id !== a.id.toLowerCase() && b.id === b.id.toLowerCase();
|
|
1033
1097
|
}
|
|
1034
1098
|
/**
|
|
1035
|
-
*
|
|
1099
|
+
* 用参数表给**发现结果**补 0 值字段。参数表里有、上游这次没返回的模型 —— 不塞进目录。
|
|
1036
1100
|
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1101
|
+
* 为什么不能把参数表一起丢进 `mergeModelLists`:那个函数按「参数更全/大小写更像注册名」
|
|
1102
|
+
* 选记录,表里的占位值(例如 OpenPangu 的 ctx 32000 / max 4000)会凭大小写规则
|
|
1039
1103
|
* 压过上游返回的真实值(ctx 524288 / max 131072)。所以顺序必须是
|
|
1040
|
-
* 「先合并发现结果 →
|
|
1104
|
+
* 「先合并发现结果 → 再用参数表补 0 值字段」。
|
|
1041
1105
|
*/
|
|
1042
|
-
function
|
|
1106
|
+
function backfillParams(discovered, params = FALLBACK_CODEARTS_MODELS) {
|
|
1043
1107
|
const byId = /* @__PURE__ */ new Map();
|
|
1044
1108
|
for (const model of discovered) byId.set(model.id.toLowerCase(), { ...model });
|
|
1045
|
-
for (const fallback of
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
byId.set(key, { ...fallback });
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
byId.set(key, {
|
|
1109
|
+
for (const fallback of params) {
|
|
1110
|
+
const current = byId.get(fallback.id.toLowerCase());
|
|
1111
|
+
if (current === void 0) continue;
|
|
1112
|
+
byId.set(fallback.id.toLowerCase(), {
|
|
1053
1113
|
id: current.id,
|
|
1054
1114
|
name: current.name || fallback.name,
|
|
1055
1115
|
contextWindow: current.contextWindow > 0 ? current.contextWindow : fallback.contextWindow,
|
|
1056
1116
|
maxTokens: current.maxTokens > 0 ? current.maxTokens : fallback.maxTokens,
|
|
1057
1117
|
supportsImages: current.supportsImages || fallback.supportsImages,
|
|
1118
|
+
...current.ratio === void 0 ? {} : { ratio: current.ratio },
|
|
1058
1119
|
...current.benefit === true ? { benefit: true } : {}
|
|
1059
1120
|
});
|
|
1060
1121
|
}
|
|
@@ -1161,10 +1222,12 @@ function signRequest(req, body, cred) {
|
|
|
1161
1222
|
* @module dsh-codearts/upstream
|
|
1162
1223
|
*/
|
|
1163
1224
|
var upstream_exports = /* @__PURE__ */ __exportAll({
|
|
1225
|
+
BENEFIT_MISSING_CODE: () => BENEFIT_MISSING_CODE,
|
|
1164
1226
|
CONCURRENCY_CAP_CODE: () => CONCURRENCY_CAP_CODE,
|
|
1165
1227
|
MAX_TOKENS_CEILING: () => MAX_TOKENS_CEILING,
|
|
1166
1228
|
PARAM_INVALID_CODE: () => PARAM_INVALID_CODE,
|
|
1167
1229
|
SNAP_HOST: () => SNAP_HOST,
|
|
1230
|
+
TPM_LIMIT_CODE: () => TPM_LIMIT_CODE,
|
|
1168
1231
|
canonicalModel: () => canonicalModel,
|
|
1169
1232
|
chatStream: () => chatStream,
|
|
1170
1233
|
claimBenefit: () => claimBenefit,
|
|
@@ -1174,13 +1237,18 @@ var upstream_exports = /* @__PURE__ */ __exportAll({
|
|
|
1174
1237
|
fetchCallerIdentity: () => fetchCallerIdentity,
|
|
1175
1238
|
fetchModels: () => fetchModels,
|
|
1176
1239
|
fetchWelfareCampaigns: () => fetchWelfareCampaigns,
|
|
1240
|
+
isBenefitMissing: () => isBenefitMissing,
|
|
1177
1241
|
isConcurrencyCap: () => isConcurrencyCap,
|
|
1178
1242
|
isParamInvalidFrame: () => isParamInvalidFrame,
|
|
1243
|
+
isQueueableRate: () => isQueueableRate,
|
|
1244
|
+
isTpmLimit: () => isTpmLimit,
|
|
1179
1245
|
isWelfareClaimedStatus: () => isWelfareClaimedStatus,
|
|
1180
1246
|
maxTokensCeilingFor: () => maxTokensCeilingFor,
|
|
1181
1247
|
pickDailyWelfareCampaign: () => pickDailyWelfareCampaign,
|
|
1182
1248
|
prepareChatBody: () => prepareChatBody,
|
|
1183
|
-
|
|
1249
|
+
rateLimitFrame: () => rateLimitFrame,
|
|
1250
|
+
signedGet: () => signedGet,
|
|
1251
|
+
upstreamErrorMessage: () => upstreamErrorMessage
|
|
1184
1252
|
});
|
|
1185
1253
|
const SNAP_HOST = "https://snap-access.cn-north-4.myhuaweicloud.com";
|
|
1186
1254
|
const EP_CHAT = "/api/v2/chat/completions";
|
|
@@ -1448,6 +1516,85 @@ const CONCURRENCY_CAP_CODE = "TM.00001041";
|
|
|
1448
1516
|
function isConcurrencyCap(result) {
|
|
1449
1517
|
return !result.ok && result.message.includes("TM.00001041");
|
|
1450
1518
|
}
|
|
1519
|
+
/**
|
|
1520
|
+
* 模型级 TPM(tokens per minute)配额超限。2026-10-21 实测:它和并发上限不同,
|
|
1521
|
+
* 是**流内首帧**(HTTP 200)回的,所以照样绕过了 classify;换号也没用(配额按
|
|
1522
|
+
* 模型共享),能做的只有本地排队等窗口滑过去。
|
|
1523
|
+
*
|
|
1524
|
+
* 上游的码并不固定:同一小时内见过 `InferHub.ModelArts.81111.429`("The model TPM
|
|
1525
|
+
* limit has been significantly exceeded.")和 `InferHub.ModelArts.81114.429`
|
|
1526
|
+
* ("Too many requests, the rate limit is 50000000 tokens per minute.")。所以按
|
|
1527
|
+
* 形状认(ModelArts.*.429)而不是按某一个常量认。
|
|
1528
|
+
*/
|
|
1529
|
+
const TPM_LIMIT_CODE = "InferHub.ModelArts.81111.429";
|
|
1530
|
+
const MODEL_RATE_CODE = /InferHub\.ModelArts\.\d+\.429/;
|
|
1531
|
+
const MODEL_RATE_TEXT = /tpm limit|rate limit|too many requests/i;
|
|
1532
|
+
function isTpmLimit(result) {
|
|
1533
|
+
if (result.ok) return false;
|
|
1534
|
+
return MODEL_RATE_CODE.test(result.message) || MODEL_RATE_TEXT.test(result.message);
|
|
1535
|
+
}
|
|
1536
|
+
/** 撞到这两类限流都值得原地等,而不是直接甩给 pi-ai 重试。 */
|
|
1537
|
+
function isQueueableRate(result) {
|
|
1538
|
+
return isConcurrencyCap(result) || isTpmLimit(result);
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* 从一帧已转成 OpenAI 形状的 SSE 里认出「可排队限流」。
|
|
1542
|
+
*
|
|
1543
|
+
* 只在**还没有任何内容产出**的首帧上使用:此时重发是安全的;一旦下游已经收到
|
|
1544
|
+
* 字节,HTTP 头也发出去了,就只能把错误帧原样交出去。
|
|
1545
|
+
*/
|
|
1546
|
+
function rateLimitFrame(text) {
|
|
1547
|
+
if (!text.includes("error")) return void 0;
|
|
1548
|
+
for (const line of text.split("\n")) {
|
|
1549
|
+
const trimmed = line.trim();
|
|
1550
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1551
|
+
const payload = trimmed.slice(5).trim();
|
|
1552
|
+
if (payload === "" || payload === "[DONE]") continue;
|
|
1553
|
+
let ev;
|
|
1554
|
+
try {
|
|
1555
|
+
ev = JSON.parse(payload);
|
|
1556
|
+
} catch {
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
const code = String(ev.error?.code ?? ev.error_code ?? "");
|
|
1560
|
+
const message = String(ev.error?.message ?? ev.error_msg ?? "");
|
|
1561
|
+
const probe = {
|
|
1562
|
+
ok: false,
|
|
1563
|
+
kind: "soft_rate",
|
|
1564
|
+
status: 429,
|
|
1565
|
+
message: `${code}: ${message}`
|
|
1566
|
+
};
|
|
1567
|
+
if (code === "TM.00001041" || isTpmLimit(probe)) return {
|
|
1568
|
+
code: code === "" ? TPM_LIMIT_CODE : code,
|
|
1569
|
+
message
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* 把上游错误翻译成用户能看懂的一句话。
|
|
1575
|
+
*
|
|
1576
|
+
* 这些错误都发生在「账号、插件都正常」的时候 —— 原样把英文码甩给用户,只会
|
|
1577
|
+
* 让人以为是自己配错了。默认分支仍保留原始信息,便于排查。
|
|
1578
|
+
*/
|
|
1579
|
+
function upstreamErrorMessage(message, status = 0) {
|
|
1580
|
+
if (MODEL_RATE_CODE.test(message) || MODEL_RATE_TEXT.test(message)) return "华为侧该模型当前请求过多(共享 TPM 超限),不是你的账号或插件问题。稍后重试,或临时改用 GLM-5.2 / GLM-5.3。";
|
|
1581
|
+
if (message.includes("TM.00001041")) return "CodeArts 免费档同时只允许 3 个会话,当前槽位一直占满(被打断的会话约 1 分钟才释放)。请稍后重试。";
|
|
1582
|
+
if (isBenefitMissing("", message)) return "这个模型属于限时福利,当前福利通道不可用(不是账号问题)。可以刷新模型列表,改用非福利模型。";
|
|
1583
|
+
if (message.toLowerCase().includes("model is not registered")) return "上游没有注册这个模型,它可能已经下线;请刷新模型列表后重选。";
|
|
1584
|
+
return `codearts 上游错误 (http ${status}): ${message.slice(0, 400)}`;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* 「这个账号当前没有该模型的限时福利权益」。2026-10-21 实测:上游回 HTTP 200,
|
|
1588
|
+
* 错误在**流内帧**里(不在状态码上),所以它既不走 retry 也不走换号,用户看到的
|
|
1589
|
+
* 就是正文里裸裸一句 `benefit not found`。
|
|
1590
|
+
*
|
|
1591
|
+
* 这条不是请求写错了,重发也不会好:福利模型只有福利路由,去掉 `maas_type: benefit`
|
|
1592
|
+
* 就变成 InferHub.002002009.404 model is not registered。唯一出路是等活动回来。
|
|
1593
|
+
*/
|
|
1594
|
+
const BENEFIT_MISSING_CODE = "InferHub.4004.200";
|
|
1595
|
+
function isBenefitMissing(code, message) {
|
|
1596
|
+
return code === "InferHub.4004.200" || message.toLowerCase().includes("benefit not found");
|
|
1597
|
+
}
|
|
1451
1598
|
function classify(status, message) {
|
|
1452
1599
|
const m = message.toLowerCase();
|
|
1453
1600
|
if (status === 401 || status === 403) return "session_dead";
|
|
@@ -1524,6 +1671,7 @@ async function chatStream(credential, prepared, signal) {
|
|
|
1524
1671
|
}
|
|
1525
1672
|
if (!resp.ok) {
|
|
1526
1673
|
const text = await resp.text().catch(() => "");
|
|
1674
|
+
if (isBenefitMissing("", text)) markBenefitBlocked(prepared.model);
|
|
1527
1675
|
return {
|
|
1528
1676
|
ok: false,
|
|
1529
1677
|
kind: classify(resp.status, text),
|
|
@@ -1582,6 +1730,7 @@ async function chatStream(credential, prepared, signal) {
|
|
|
1582
1730
|
continue;
|
|
1583
1731
|
}
|
|
1584
1732
|
if (ev.error_code && ev.error_code !== "0" && ev.error_code !== 0) {
|
|
1733
|
+
if (isBenefitMissing(String(ev.error_code), String(ev.error_msg ?? ""))) markBenefitBlocked(model);
|
|
1585
1734
|
controller.enqueue(openAIData({
|
|
1586
1735
|
choices: [{
|
|
1587
1736
|
delta: { content: "" },
|
|
@@ -1727,6 +1876,21 @@ function asArray(value) {
|
|
|
1727
1876
|
return Array.isArray(value) ? value : [];
|
|
1728
1877
|
}
|
|
1729
1878
|
/**
|
|
1879
|
+
* 从 `credit[]` 里取常规时段的展示倍率(原样 `0.18x`)。
|
|
1880
|
+
*
|
|
1881
|
+
* 上游给每个时段一条(`default` / `off_peak`,实测 2026-10-21)。这里只取
|
|
1882
|
+
* `default`:尖峰价才是用户平时付的,低峰 0.09x 属于额外优惠,拿来当标题会低估。
|
|
1883
|
+
* 没有 credit 的模型(如 Qwen3-VL)返回 undefined —— 界面上就不显示倍率。
|
|
1884
|
+
*/
|
|
1885
|
+
function creditRatioOf(record) {
|
|
1886
|
+
const credits = asArray(record.credit).map(asRecord);
|
|
1887
|
+
if (credits.length === 0) return void 0;
|
|
1888
|
+
const pick = credits.find((entry) => entry.time_slot_id === "default") ?? credits.find((entry) => entry.time_slot_id === null || entry.time_slot_id === void 0) ?? credits[0];
|
|
1889
|
+
if (pick === void 0) return void 0;
|
|
1890
|
+
const match = /^\s*(\d+(?:\.\d+)?)\s*x\s*$/i.exec(firstString(pick.ratio_display));
|
|
1891
|
+
return match === null ? void 0 : `${match[1]}x`;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1730
1894
|
* 内置模型:`GET /v1/model/builtin`(`Agent-Type: PromptCenter`)。
|
|
1731
1895
|
* 实测响应 `{ count, builtinModels: [{ model_id, model_name, model_desc,
|
|
1732
1896
|
* enable, display_enabled, supports_images, ... }] }` —— 注意它**不带**
|
|
@@ -1742,12 +1906,14 @@ async function fetchBuiltinModels(credential, signal) {
|
|
|
1742
1906
|
if (model.enable === false) continue;
|
|
1743
1907
|
const id = firstString(model.model_id, model.model_name);
|
|
1744
1908
|
if (id === "") continue;
|
|
1909
|
+
const ratio = creditRatioOf(model);
|
|
1745
1910
|
out.push({
|
|
1746
1911
|
id,
|
|
1747
1912
|
name: firstString(model.model_name, id),
|
|
1748
1913
|
contextWindow: positive(model.context_window),
|
|
1749
1914
|
maxTokens: positive(model.max_tokens),
|
|
1750
|
-
supportsImages: model.supports_images === true
|
|
1915
|
+
supportsImages: model.supports_images === true,
|
|
1916
|
+
...ratio === void 0 ? {} : { ratio }
|
|
1751
1917
|
});
|
|
1752
1918
|
}
|
|
1753
1919
|
return out;
|
|
@@ -1768,12 +1934,14 @@ async function fetchBenefitModels(credential, signal) {
|
|
|
1768
1934
|
const model = asRecord(raw);
|
|
1769
1935
|
const id = firstString(model.model_id, model.model_name);
|
|
1770
1936
|
if (id === "") continue;
|
|
1937
|
+
const ratio = creditRatioOf(model);
|
|
1771
1938
|
out.push({
|
|
1772
1939
|
id,
|
|
1773
1940
|
name: firstString(model.model_name, id),
|
|
1774
1941
|
contextWindow: positive(model.context_window),
|
|
1775
1942
|
maxTokens: positive(model.max_tokens),
|
|
1776
1943
|
supportsImages: model.supports_images === true,
|
|
1944
|
+
...ratio === void 0 ? {} : { ratio },
|
|
1777
1945
|
benefit: true
|
|
1778
1946
|
});
|
|
1779
1947
|
}
|
|
@@ -1813,12 +1981,14 @@ async function fetchAgentModels(credential, signal) {
|
|
|
1813
1981
|
const params = asRecord(model.model_parameters);
|
|
1814
1982
|
const id = firstString(params.model_id, model.model_id, model.model_alias, model.model_name);
|
|
1815
1983
|
if (id === "") continue;
|
|
1984
|
+
const ratio = creditRatioOf(model) ?? creditRatioOf(params);
|
|
1816
1985
|
out.push({
|
|
1817
1986
|
id,
|
|
1818
1987
|
name: firstString(model.model_name, model.model_alias, id),
|
|
1819
1988
|
contextWindow: positive(params.context_window),
|
|
1820
1989
|
maxTokens: positive(params.max_tokens),
|
|
1821
|
-
supportsImages: model.supports_images === true
|
|
1990
|
+
supportsImages: model.supports_images === true,
|
|
1991
|
+
...ratio === void 0 ? {} : { ratio }
|
|
1822
1992
|
});
|
|
1823
1993
|
}
|
|
1824
1994
|
return out;
|
|
@@ -1874,7 +2044,7 @@ async function fetchModels(credential, signal) {
|
|
|
1874
2044
|
discovered.push(item.models);
|
|
1875
2045
|
}
|
|
1876
2046
|
if (sources.length === 0) throw new Error(`model discovery failed on every source: ${errors.map((e) => `${e.source}: ${e.message}`).join("; ")}`);
|
|
1877
|
-
const merged =
|
|
2047
|
+
const merged = backfillParams(mergeModelLists(discovered));
|
|
1878
2048
|
if (merged.length === 0) throw new Error("model discovery returned an empty catalog");
|
|
1879
2049
|
return {
|
|
1880
2050
|
models: merged.map((model) => ({
|
|
@@ -2181,8 +2351,14 @@ async function discoverModels(store, signal) {
|
|
|
2181
2351
|
async function refreshCatalog(store, catalog, signal) {
|
|
2182
2352
|
const outcome = await discoverModels(store, signal);
|
|
2183
2353
|
if (!outcome.anySucceeded || outcome.models.length === 0) return outcome;
|
|
2184
|
-
catalog.set(outcome.models);
|
|
2185
2354
|
setDiscoveredBenefitModels(outcome.models.filter((model) => model.benefit === true).map((model) => model.id));
|
|
2355
|
+
catalog.set(outcome.models.map((model) => {
|
|
2356
|
+
const benefit = isBenefitModel(model.id);
|
|
2357
|
+
return {
|
|
2358
|
+
...model,
|
|
2359
|
+
name: modelDisplayName(model.name, benefit, model.ratio)
|
|
2360
|
+
};
|
|
2361
|
+
}));
|
|
2186
2362
|
return outcome;
|
|
2187
2363
|
}
|
|
2188
2364
|
//#endregion
|
|
@@ -2404,4 +2580,4 @@ function sumOf(store) {
|
|
|
2404
2580
|
return out;
|
|
2405
2581
|
}
|
|
2406
2582
|
//#endregion
|
|
2407
|
-
export { isBenefitModel as C,
|
|
2583
|
+
export { isBenefitModel as A, upstreamErrorMessage as C, creditRatioLabel as D, clearBenefitBlocked as E, startLogin as F, CodeArtsCredentialStore as M, codeartsAccountsPath as N, filterEnabledModels as O, codeartsAuthPath as P, signedGet as S, FALLBACK_CODEARTS_MODELS as T, isWelfareClaimedStatus as _, SNAP_HOST as a, prepareChatBody as b, claimDailyWelfare as c, fetchModels as d, fetchWelfareCampaigns as f, isTpmLimit as g, isQueueableRate as h, refreshCatalog as i, CodeArtsAccountStore as j, isBenefitBlocked as k, degradeTokenCeiling as l, isParamInvalidFrame as m, codeartsUsagePath as n, canonicalModel as o, isConcurrencyCap as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, fetchCallerIdentity as u, maxTokensCeilingFor as v, CodeArtsCatalog as w, rateLimitFrame as x, pickDailyWelfareCampaign as y };
|
package/package.json
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"name": "dsh-codearts",
|
|
3
3
|
"displayName": "DSH CodeArts Connect",
|
|
4
4
|
"description": "把华为云 CodeArts 接入 DeepSeek Harness —— 内置 OAuth2 登录(PKCE + DPoP),登录后即可在 DSH 对话窗口使用 CodeArts 的模型。",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.3",
|
|
6
|
+
"packageManager": "pnpm@11.24.0",
|
|
6
7
|
"author": "elegant01",
|
|
7
8
|
"keywords": [
|
|
8
9
|
"dsh-plugin",
|