dsh-connect-modelscope-token-plan 0.4.0 → 0.5.0
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/CHANGELOG.md +30 -0
- package/README.md +18 -12
- package/assets/tab-account-and-usage.png +0 -0
- package/assets/tab-model-draw.png +0 -0
- package/assets/tab-model-list.png +0 -0
- package/client.js +632 -150
- package/cordis.patch.yml +9 -4
- package/lib/host-config.js +138 -6
- package/lib/index.js +586 -143
- package/lib/llm-adapter.js +38 -18
- package/package.json +1 -1
- package/screenshots.json +2 -1
- package/assets/icon-preview.png +0 -0
package/cordis.patch.yml
CHANGED
|
@@ -24,8 +24,12 @@
|
|
|
24
24
|
# 删除——留着它们只会让人以为面板能算「还剩几次」,而它算不出。
|
|
25
25
|
# 本地计数的独家价值在分布 / 趋势 / 429 事件流,不在「离上限多远」。
|
|
26
26
|
# ------------------------------------------------------------------
|
|
27
|
-
#
|
|
27
|
+
# 趋势图显示天数(面板标题「近 N 天」与图上柱数)——面板里可改看多长。
|
|
28
28
|
trendDays: 14
|
|
29
|
+
# 本地趋势**保留**天数(插件私有状态文件里的天桶数量,与上游无关)。
|
|
30
|
+
# 独立于 trendDays:显示只决定「画多少」,保留决定「留多少」。留得比画得多,
|
|
31
|
+
# 面板才可能让你把窗口调到更远。显示窗口恒被硬夹在保留之内。
|
|
32
|
+
retentionDays: 90
|
|
29
33
|
# 429/错误事件环形缓冲的上限(面板只展示最近的)。上界 1000,硬夹。
|
|
30
34
|
maxEvents: 50
|
|
31
35
|
# Host 侧对 /v1/models 的缓存秒数;面板脚注直接引用此值。上界 600。
|
|
@@ -77,13 +81,14 @@
|
|
|
77
81
|
# 没有任何 text-to-image。官方示例里的 Qwen/Qwen-Image-2.1-Turbo 与
|
|
78
82
|
# black-forest-labs/FLUX.1-Kontext-dev **都不在目录内**,后者实测 FAILED。
|
|
79
83
|
drawModelId: ""
|
|
80
|
-
# 单次出图总预算(毫秒),含提交与全部轮询。上界
|
|
81
|
-
#
|
|
82
|
-
#
|
|
84
|
+
# 单次出图总预算(毫秒),含提交与全部轮询。上界 1800000(30 分钟),
|
|
85
|
+
# 严格高于这个默认值——它**不是**上限。别调小:实测最慢一次 859s 才成功,
|
|
86
|
+
# 而超时放弃**不等于**任务死亡(上游会继续跑),把预算压到 5 分钟曾实测击穿。
|
|
83
87
|
drawTimeoutMs: 900000
|
|
84
88
|
# 单次出图**工具调用**愿意等的时间(毫秒)。与上面那条是两件事:那是
|
|
85
89
|
# 「这张图值不值得继续等」的总放弃线,这是「一轮对话被占住多久」。
|
|
86
90
|
# 等不到就返回 pending + jobId,agent 带 jobId 再调用即可续查。
|
|
91
|
+
# 实际取值夹在 10s 与上面那条 drawTimeoutMs 之间(等待超过总放弃线没有意义)。
|
|
87
92
|
drawWaitMs: 90000
|
|
88
93
|
# 追加可信 Host 名(默认 localhost / 127.0.0.1 / ::1,只增不替)。
|
|
89
94
|
# allowedHosts: []
|
package/lib/host-config.js
CHANGED
|
@@ -482,6 +482,22 @@ const KNOWN_VISION_IDS = Object.freeze(/* @__PURE__ */ new Set([
|
|
|
482
482
|
"stepfun-ai/Step-3.7-Flash"
|
|
483
483
|
]));
|
|
484
484
|
/**
|
|
485
|
+
* 已知能**纯文生图**的目录内模型 id(2026-10-16 真机复核)。
|
|
486
|
+
*
|
|
487
|
+
* 为什么需要手工策展:api-inference 目录里至今**没有**平台声明的 `text-to-image`
|
|
488
|
+
* 条目,只有 2 条 `image-to-image`——而两者行为**不同**:
|
|
489
|
+
* `Qwen/Qwen-Image-Edit` 不带 `image_url` 纯文生图可用(本轮实测:提交成功、两次
|
|
490
|
+
* 15min 排队超时后于 9.7min 出图);`MusePublic/Qwen-Image-Edit` 纯文生图被上游
|
|
491
|
+
* 直接拒绝(提交阶段 HTTP 4xx:`image_url is required for model: …`)。
|
|
492
|
+
* 能力枚举把两者都归为 `image-to-image`,区分不了这个差异,所以这里用实测知识兜底:
|
|
493
|
+
* 目录里没有 `text-to-image` 可挑时,默认偏向这个已知能用的 id,而不是「目录第一个」
|
|
494
|
+
* (真实目录顺序 `MusePublic/…` 在前,照旧逻辑纯文生图必挂)。
|
|
495
|
+
*
|
|
496
|
+
* 与 `KNOWN_VISION_IDS` 放在一起:所有「哪些模型能做什么」的策展知识集中一处,
|
|
497
|
+
* 不会漂。
|
|
498
|
+
*/
|
|
499
|
+
const KNOWN_T2I_MODEL_ID = "Qwen/Qwen-Image-Edit";
|
|
500
|
+
/**
|
|
485
501
|
* 把原始 `/v1/models` data 条目变成归一目录条目。
|
|
486
502
|
*
|
|
487
503
|
* 只做投影与回退,不做判断:`id` 必取,`name` 回退 id,`vision`/`contextWindow`/
|
|
@@ -899,6 +915,85 @@ function textToImageModelIds(entries) {
|
|
|
899
915
|
return idsOf(entries, (entry) => resolveModelCapability(entry) === "text-to-image");
|
|
900
916
|
}
|
|
901
917
|
/**
|
|
918
|
+
* 一份目录里的出图候选,按首见顺序去重,带能力档与纯文生图标记。
|
|
919
|
+
*
|
|
920
|
+
* 与 {@link imageGenModelIds} 同一份判定(只认平台声明的出图能力),只是把
|
|
921
|
+
* `reference` 需要的两个附加位一起投出来——面板的每一行徽章与「当前使用」都从
|
|
922
|
+
* 这里派生,不各自再判一次。
|
|
923
|
+
* @param {unknown} entries - 归一目录条目。
|
|
924
|
+
* @returns {DrawCandidate[]} 出图候选。
|
|
925
|
+
*/
|
|
926
|
+
function drawCandidates(entries) {
|
|
927
|
+
const position = /* @__PURE__ */ new Map();
|
|
928
|
+
const out = [];
|
|
929
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
930
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
931
|
+
const e = entry;
|
|
932
|
+
if (!isImageGenModel(e)) continue;
|
|
933
|
+
const id = str(e.id, "");
|
|
934
|
+
if (id === "") continue;
|
|
935
|
+
const capability = resolveModelCapability(e);
|
|
936
|
+
const row = {
|
|
937
|
+
id,
|
|
938
|
+
capability: capability === "text-to-image" ? "text-to-image" : capability === "image-to-image" ? "image-to-image" : "other",
|
|
939
|
+
textToImage: capability === "text-to-image" || id === "Qwen/Qwen-Image-Edit"
|
|
940
|
+
};
|
|
941
|
+
const seen = position.get(id);
|
|
942
|
+
if (seen === void 0) {
|
|
943
|
+
position.set(id, out.length);
|
|
944
|
+
out.push(row);
|
|
945
|
+
} else out[seen] = row;
|
|
946
|
+
}
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 从目录里挑一个出图模型,并说明**为什么是它**。
|
|
951
|
+
*
|
|
952
|
+
* 优先级(高 → 低),与 `draw-runtime.ts` 的 `pickDrawModel` 同一份:
|
|
953
|
+
* 1. `requested` —— 工具调用里显式给的 model(这里不投理由,调用方场景);
|
|
954
|
+
* 2. `preferred` —— 面板保存 / 配置 `drawModelId`;用户点了谁就是谁,不校验在不在目录;
|
|
955
|
+
* 3. 平台声明为 `text-to-image` 的条目(将来目录补上真文生图模型时自动优先);
|
|
956
|
+
* 4. 已知能纯文生图的 `KNOWN_T2I_MODEL_ID`(今天目录的坑位);
|
|
957
|
+
* 5. 目录里第一个出图模型。
|
|
958
|
+
*
|
|
959
|
+
* `requested` 会以 `preferred` 的理由返回——它同样是「有人指定了」,面板不需要
|
|
960
|
+
* 为一次工具调用区分这两者。
|
|
961
|
+
* @param {unknown} entries - 归一目录条目。
|
|
962
|
+
* @param {string} [requested] - 工具调用显式指定的 id。
|
|
963
|
+
* @param {string} [preferred] - 面板/配置的默认 id。
|
|
964
|
+
* @returns {DrawModelChoice} 选中的 id 与理由。
|
|
965
|
+
*/
|
|
966
|
+
function resolveDrawModelChoice(entries, requested, preferred) {
|
|
967
|
+
const want = str(requested, "");
|
|
968
|
+
if (want !== "") return {
|
|
969
|
+
id: want,
|
|
970
|
+
source: "preferred"
|
|
971
|
+
};
|
|
972
|
+
const preferredId = str(preferred, "");
|
|
973
|
+
if (preferredId !== "") return {
|
|
974
|
+
id: preferredId,
|
|
975
|
+
source: "preferred"
|
|
976
|
+
};
|
|
977
|
+
const ids = imageGenModelIds(entries);
|
|
978
|
+
if (ids.length === 0) return {
|
|
979
|
+
id: null,
|
|
980
|
+
source: null
|
|
981
|
+
};
|
|
982
|
+
const t2i = textToImageModelIds(entries);
|
|
983
|
+
if (t2i.length > 0) return {
|
|
984
|
+
id: t2i[0] ?? null,
|
|
985
|
+
source: "declared-text-to-image"
|
|
986
|
+
};
|
|
987
|
+
if (ids.includes("Qwen/Qwen-Image-Edit")) return {
|
|
988
|
+
id: KNOWN_T2I_MODEL_ID,
|
|
989
|
+
source: "known-text-to-image"
|
|
990
|
+
};
|
|
991
|
+
return {
|
|
992
|
+
id: ids[0] ?? null,
|
|
993
|
+
source: "catalog-first"
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
902
997
|
* 带额度可用性的面板 roster。
|
|
903
998
|
*
|
|
904
999
|
* 与 {@link rosterOf} 一样每 chat 模型 id 一行,但每行还带该模型额度池是否耗尽。
|
|
@@ -949,7 +1044,7 @@ const inject = ["webServer"];
|
|
|
949
1044
|
* 产物反而引入第二份真源;这里用常量、由 test/config.test.mjs 钉住与
|
|
950
1045
|
* package.json 一致。
|
|
951
1046
|
*/
|
|
952
|
-
const PLUGIN_VERSION = "0.
|
|
1047
|
+
const PLUGIN_VERSION = "0.5.0";
|
|
953
1048
|
/**
|
|
954
1049
|
* 默认值。刻意不含额度常数——理由见本文件头。
|
|
955
1050
|
* 面板头条是官方魔粒余额,次数口径的推算已在面板收敛时删除,这里若留着
|
|
@@ -960,8 +1055,23 @@ const CONFIG_DEFAULTS = Object.freeze({
|
|
|
960
1055
|
apiBase: "https://api-inference.modelscope.cn/v1",
|
|
961
1056
|
/** 站点源站,只用于面板外链(访问令牌页 / 文档)。 */
|
|
962
1057
|
siteBase: "https://modelscope.cn",
|
|
963
|
-
/**
|
|
1058
|
+
/**
|
|
1059
|
+
* 趋势图**显示**天数(面板标题「近 N 天」与图上柱数)——默认值,用户可在面板里
|
|
1060
|
+
* 改看多长。
|
|
1061
|
+
*
|
|
1062
|
+
* **与 retentionDays 是两件事**(2026-10-17 拆开):这个数只决定「画多少」,
|
|
1063
|
+
* 不再决定「留多少」。曾经一个 trendDays 同时是保留窗口,于是「想多看几天」等于
|
|
1064
|
+
* 「改保留策略」,改小还会把历史当场剪掉——拆开正是为了消除这层耦合。
|
|
1065
|
+
*/
|
|
964
1066
|
trendDays: 14,
|
|
1067
|
+
/**
|
|
1068
|
+
* 本地趋势**保留**天数(usage-store 天桶的修剪窗口;超出即删)。
|
|
1069
|
+
*
|
|
1070
|
+
* 取 90:够回看一个季度,而每个天桶只是两个整数——留 90 天的磁盘代价可忽略。
|
|
1071
|
+
* 显示窗口恒被夹在它之内(见 resolveSettings),所以「画得比留得多」这个会把
|
|
1072
|
+
* 已剪的日子画成 0(与「那天真的没调用」在面板上无法区分)的组合不会出现。
|
|
1073
|
+
*/
|
|
1074
|
+
retentionDays: 90,
|
|
965
1075
|
/** Host 侧对 /v1/models 的缓存秒数。 */
|
|
966
1076
|
cacheSeconds: 60,
|
|
967
1077
|
/** 面板轮询间隔——由 Host 下发、面板跟随,两端不各自假设。 */
|
|
@@ -995,6 +1105,10 @@ const CONFIG_DEFAULTS = Object.freeze({
|
|
|
995
1105
|
* 取 15 分钟:覆盖已观测到的最慢一次(537s)并留一倍余量。**注意超时放弃不等于
|
|
996
1106
|
* 任务死亡**——上游任务会继续跑(且记录最终会被回收,见 docs/SPIKE-image-gen.md
|
|
997
1107
|
* §坑 5)。宁可多等,不要把一张已经在生成的图扔掉。
|
|
1108
|
+
*
|
|
1109
|
+
* 「15 分钟够不够」我们**没有把握**(上面的实测还在恶化),所以夹取的上界
|
|
1110
|
+
* `DRAW_TIMEOUT_MAX_MS` 取 30 分钟,严格高于本默认值——觉得不够就往上调,
|
|
1111
|
+
* 别把它当上限用。
|
|
998
1112
|
*/
|
|
999
1113
|
drawTimeoutMs: 9e5,
|
|
1000
1114
|
/**
|
|
@@ -1023,6 +1137,20 @@ function clampInt(raw, def, min, max = Infinity) {
|
|
|
1023
1137
|
return Math.min(max, Math.max(min, Math.floor(num(raw, def))));
|
|
1024
1138
|
}
|
|
1025
1139
|
/**
|
|
1140
|
+
* `drawTimeoutMs` 的**上界**——必须严格大于它的默认值。
|
|
1141
|
+
*
|
|
1142
|
+
* 这个上界曾经与默认值同为 900_000,于是该字段只能调小、不能调大。而「15 分钟
|
|
1143
|
+
* 够不够」恰恰是本文件自己承认**没有把握**的问题:上面的注释记着同模型连测
|
|
1144
|
+
* 61s→261s→**537s** 的单调恶化、5 分钟预算被实测击穿,docs/SPIKE-image-gen.md
|
|
1145
|
+
* 还记着一次 900s 预算的 e2e 在 905s 放弃。把一个**已知可能不够用**的数值同时
|
|
1146
|
+
* 写成默认值与硬上限,等于在用户最需要「多等一会儿」的时刻把旋钮焊死——而且
|
|
1147
|
+
* 焊死在一个已被实测证伪的数上。
|
|
1148
|
+
*
|
|
1149
|
+
* 取 30 分钟:仍是有界的(不会让一次工具调用无限期挂着),但比默认值高出一倍,
|
|
1150
|
+
* 慢档用户有可调空间。它同时是 `drawWaitMs` 的上界来源(见 resolveSettings)。
|
|
1151
|
+
*/
|
|
1152
|
+
const DRAW_TIMEOUT_MAX_MS = 18e5;
|
|
1153
|
+
/**
|
|
1026
1154
|
* 端点必须是合法的 http(s) 绝对地址;不是就在**挂载时**抛(resolveSettings
|
|
1027
1155
|
* 捕获后转成 configError 经快照呈现),而不是等到第一次轮询才变成莫名其妙的
|
|
1028
1156
|
* 网络失败。
|
|
@@ -1049,11 +1177,14 @@ function resolveSettings(config) {
|
|
|
1049
1177
|
const siteBase = str(source.siteBase, CONFIG_DEFAULTS.siteBase).replace(/\/+$/, "");
|
|
1050
1178
|
assertHttpUrl(apiBase, "apiBase");
|
|
1051
1179
|
assertHttpUrl(siteBase, "siteBase");
|
|
1180
|
+
const drawTimeoutMs = clampInt(source.drawTimeoutMs, CONFIG_DEFAULTS.drawTimeoutMs, 3e4, DRAW_TIMEOUT_MAX_MS);
|
|
1181
|
+
const retentionDays = clampInt(source.retentionDays, CONFIG_DEFAULTS.retentionDays, 1, 365);
|
|
1052
1182
|
return {
|
|
1053
1183
|
settings: {
|
|
1054
1184
|
apiBase,
|
|
1055
1185
|
siteBase,
|
|
1056
|
-
trendDays: clampInt(source.trendDays, CONFIG_DEFAULTS.trendDays, 1,
|
|
1186
|
+
trendDays: clampInt(source.trendDays, CONFIG_DEFAULTS.trendDays, 1, retentionDays),
|
|
1187
|
+
retentionDays,
|
|
1057
1188
|
cacheSeconds: clampInt(source.cacheSeconds, CONFIG_DEFAULTS.cacheSeconds, 5, 600),
|
|
1058
1189
|
pollSeconds: clampInt(source.pollSeconds, CONFIG_DEFAULTS.pollSeconds, 5, 3600),
|
|
1059
1190
|
inferenceTimeoutMs: clampInt(source.inferenceTimeoutMs, CONFIG_DEFAULTS.inferenceTimeoutMs, 1e3, 6e5),
|
|
@@ -1061,8 +1192,8 @@ function resolveSettings(config) {
|
|
|
1061
1192
|
registerProvider: source.registerProvider === true,
|
|
1062
1193
|
drawEnabled: source.drawEnabled === true,
|
|
1063
1194
|
drawModelId: str(source.drawModelId, CONFIG_DEFAULTS.drawModelId),
|
|
1064
|
-
drawTimeoutMs
|
|
1065
|
-
drawWaitMs: clampInt(source.drawWaitMs, CONFIG_DEFAULTS.drawWaitMs, 1e4,
|
|
1195
|
+
drawTimeoutMs,
|
|
1196
|
+
drawWaitMs: clampInt(source.drawWaitMs, CONFIG_DEFAULTS.drawWaitMs, 1e4, drawTimeoutMs),
|
|
1066
1197
|
allowedHosts: resolveAllowedHosts(source)
|
|
1067
1198
|
},
|
|
1068
1199
|
configError: null
|
|
@@ -1073,6 +1204,7 @@ function resolveSettings(config) {
|
|
|
1073
1204
|
apiBase: CONFIG_DEFAULTS.apiBase,
|
|
1074
1205
|
siteBase: CONFIG_DEFAULTS.siteBase,
|
|
1075
1206
|
trendDays: CONFIG_DEFAULTS.trendDays,
|
|
1207
|
+
retentionDays: CONFIG_DEFAULTS.retentionDays,
|
|
1076
1208
|
cacheSeconds: CONFIG_DEFAULTS.cacheSeconds,
|
|
1077
1209
|
pollSeconds: CONFIG_DEFAULTS.pollSeconds,
|
|
1078
1210
|
inferenceTimeoutMs: CONFIG_DEFAULTS.inferenceTimeoutMs,
|
|
@@ -1148,4 +1280,4 @@ function isAdmitted(request, allowedHosts) {
|
|
|
1148
1280
|
}
|
|
1149
1281
|
|
|
1150
1282
|
//#endregion
|
|
1151
|
-
export {
|
|
1283
|
+
export { errMsg as C, redactSecrets as D, pluginError as E, str as O, degrade as S, optional as T, parseAllowedList as _, resolveSettings as a, looksLikeRateLimit as b, LLM_PROVIDER_ID as c, imageGenModelIds as d, isVisionModel as f, enabledCountOf as g, rosterWithAvailability as h, name as i, verbatim as k, buildDescriptors as l, resolveDrawModelChoice as m, inject as n, LLM_API_KEY_NAME as o, normalizeEntry as p, isAdmitted as r, LLM_DISPLAY_NAME as s, PLUGIN_VERSION as t, drawCandidates as u, CODE as v, obj as w, reclassifyStream as x, hasHardQuotaWordingIn as y };
|