dsh-xdpool 2.5.0 → 2.5.1
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 +41 -52
- package/README.en.md +11 -2
- package/README.md +12 -3
- package/assets/community-qr.png +0 -0
- package/cordis.patch.yml +17 -41
- package/lib/bin.js +194 -30
- package/lib/client.js +285 -774
- package/lib/index.d.ts +54 -82
- package/lib/index.js +276 -409
- package/package.json +1 -4
- package/vendor/channel-pack/pack.js +8 -124
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-xdpool",
|
|
3
3
|
"displayName": "DSH XD Pool",
|
|
4
4
|
"description": "One DSH model pool for every free quota you have: merge all locally signed-in WorkBuddy accounts with auto-failover, sign in to 13 vendors by QR/web, aggregate 76 vendors' free tiers, plus a usage dashboard, model routing, a local OpenAI-compatible gateway and a points-automation suite.",
|
|
5
|
-
"version": "2.5.
|
|
5
|
+
"version": "2.5.1",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "XDTrees",
|
|
8
8
|
"homepage": "https://github.com/XDTrees/dsh-xdpool#readme",
|
|
@@ -75,9 +75,6 @@
|
|
|
75
75
|
"platform": "web"
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
|
-
"optionalDependencies": {
|
|
79
|
-
"billion-context": "^0.1.197"
|
|
80
|
-
},
|
|
81
78
|
"devDependencies": {
|
|
82
79
|
"@deepseek-ai/cordis": "4.0.2",
|
|
83
80
|
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
@@ -29453,15 +29453,6 @@ var channelPackSchema = Schema.object({
|
|
|
29453
29453
|
// **必须显式声明**:SettingsStore 的 `scope.replace()` 是整体覆盖,
|
|
29454
29454
|
// schema 里没声明的键会被 schemastery 剥掉,写回时就此静默丢失。
|
|
29455
29455
|
contextBudgets: Schema.dict(Schema.any()).default({}),
|
|
29456
|
-
// ── 本地扩展(dsh-xdpool,2026-10-26)────────────────────────────────
|
|
29457
|
-
// 记录「用户亲手动过这个 provider 的模型选择」,用来把「用户把模型全部
|
|
29458
|
-
// 打开」和「用户从未选择过」区分开。`disabledModels` 本身做不到这件事:
|
|
29459
|
-
// 打开模型时我们**删除**键而不是写 false(见 setModelDisabled 注释),
|
|
29460
|
-
// 所以「全开」与「从未碰过」都表现为空表。启动补静音原先拿
|
|
29461
|
-
// `disabledModelsFor(provider).size === 0` 当判据,于是把「全开」当成
|
|
29462
|
-
// 「从未选择」,每次重启又把整目录静音回去 —— 目录越小的渠道越必然中招
|
|
29463
|
-
// (zcode 只有两个模型,用户勾满之后每次重启都变回全禁用)。
|
|
29464
|
-
selectionTouched: Schema.dict(Schema.any()).default({}),
|
|
29465
29456
|
loomyPermanentLocked: Schema.boolean().default(false),
|
|
29466
29457
|
gatewayEnabled: Schema.boolean().default(true),
|
|
29467
29458
|
providerOrder: Schema.array(Schema.string()).default([])
|
|
@@ -29520,14 +29511,6 @@ function sanitizeContextBudgets(raw) {
|
|
|
29520
29511
|
}
|
|
29521
29512
|
return result;
|
|
29522
29513
|
}
|
|
29523
|
-
function sanitizeSelectionTouched(raw) {
|
|
29524
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
29525
|
-
const result = {};
|
|
29526
|
-
for (const [provider, value] of Object.entries(raw)) {
|
|
29527
|
-
if (value === true && provider.length > 0) result[provider] = true;
|
|
29528
|
-
}
|
|
29529
|
-
return result;
|
|
29530
|
-
}
|
|
29531
29514
|
function sanitizeAccounts(raw) {
|
|
29532
29515
|
if (!Array.isArray(raw)) return [];
|
|
29533
29516
|
const accounts = [];
|
|
@@ -29559,7 +29542,6 @@ var SettingsStore = class {
|
|
|
29559
29542
|
accounts: sanitizeAccounts(value.accounts),
|
|
29560
29543
|
disabledModels: sanitizeDisabledModels(value.disabledModels),
|
|
29561
29544
|
contextBudgets: sanitizeContextBudgets(value.contextBudgets),
|
|
29562
|
-
selectionTouched: sanitizeSelectionTouched(value.selectionTouched),
|
|
29563
29545
|
// 只是**镜像**(权威表在 permanent-locks.json);老文档没这个键 → false。
|
|
29564
29546
|
loomyPermanentLocked: value.loomyPermanentLocked === true,
|
|
29565
29547
|
// 老文档没这个键 → 启用,与升级前行为一致。
|
|
@@ -29575,9 +29557,6 @@ var SettingsStore = class {
|
|
|
29575
29557
|
// 预算表丢失的后果只是「下次选择器回到原生窗口」,用户重选一次即可
|
|
29576
29558
|
// (纯偏好、可逆),故接受老版本重写时丢掉本键。
|
|
29577
29559
|
contextBudgets: state.contextBudgets ?? {},
|
|
29578
|
-
// 丢了这个标记的后果是「下次启动把该渠道重新静音一次」,用户再勾一遍
|
|
29579
|
-
// 就会重新置位,属可逆,故与 contextBudgets 同样接受老版本重写时丢掉。
|
|
29580
|
-
selectionTouched: state.selectionTouched ?? {},
|
|
29581
29560
|
// 镜像字段由 AccountPool 与独立文档**同源写出**:同机上只认这个字段的
|
|
29582
29561
|
// 旧版本代码(其它 profile)读它、也会原样写回它,故两边不会脱节。
|
|
29583
29562
|
loomyPermanentLocked: state.loomyPermanentLocked === true,
|
|
@@ -29682,7 +29661,6 @@ var FileStore = class {
|
|
|
29682
29661
|
accounts: sanitizeAccounts(value.accounts),
|
|
29683
29662
|
disabledModels: sanitizeDisabledModels(value.disabledModels),
|
|
29684
29663
|
contextBudgets: sanitizeContextBudgets(value.contextBudgets),
|
|
29685
|
-
selectionTouched: sanitizeSelectionTouched(value.selectionTouched),
|
|
29686
29664
|
// 只是镜像(权威表在 permanent-locks.json);老文档没这个键 → false。
|
|
29687
29665
|
loomyPermanentLocked: value.loomyPermanentLocked === true,
|
|
29688
29666
|
// 老文档没这个键 → 启用,与升级前行为一致。
|
|
@@ -29727,9 +29705,6 @@ var FileStore = class {
|
|
|
29727
29705
|
accounts,
|
|
29728
29706
|
disabledModels: {},
|
|
29729
29707
|
contextBudgets: {},
|
|
29730
|
-
// 从凭据文件重建的文档不含任何选择历史 → 空表,让补静音照常生效
|
|
29731
|
-
// (这些账号确实是「从未选择过」,正是补静音要照顾的情形)。
|
|
29732
|
-
selectionTouched: {},
|
|
29733
29708
|
// 凭据文件里没有任何锁定信息 → 镜像写 false(权威表另有其文档)。
|
|
29734
29709
|
loomyPermanentLocked: false,
|
|
29735
29710
|
// 恢复出的文档本来就不含任何开关信息 → 启用(与全新安装一致)。
|
|
@@ -29881,17 +29856,6 @@ var AccountPool = class {
|
|
|
29881
29856
|
* 把窗口收窄到 200K 这类值」,不改变上游真实能力。
|
|
29882
29857
|
*/
|
|
29883
29858
|
contextBudgetCache = {};
|
|
29884
|
-
/**
|
|
29885
|
-
* 「用户动过该 provider 的模型选择」标记的进程内权威副本。
|
|
29886
|
-
*
|
|
29887
|
-
* 存在的唯一理由是把「用户把模型全部打开」与「用户从未选择过」区分开:
|
|
29888
|
-
* 打开模型时黑名单里**删除**键(见 {@link setModelDisabled}),所以「全开」
|
|
29889
|
-
* 与「从未碰过」的 `disabledModels` 都是空表,单看黑名单无法分辨,启动补
|
|
29890
|
-
* 静音就会把「全开」当成「从未选择」再静音一次。
|
|
29891
|
-
*
|
|
29892
|
-
* ⚠️ 只记录已置位的 provider(缺键 = 从未选择)。
|
|
29893
|
-
*/
|
|
29894
|
-
selectionTouchedCache = {};
|
|
29895
29859
|
/**
|
|
29896
29860
|
* 「锁定永久积分」开关表的**权威进程内副本**(与 {@link cache} 同理)。
|
|
29897
29861
|
*
|
|
@@ -29943,7 +29907,6 @@ var AccountPool = class {
|
|
|
29943
29907
|
this.cache = state.accounts;
|
|
29944
29908
|
this.modelCache = state.disabledModels;
|
|
29945
29909
|
this.contextBudgetCache = state.contextBudgets ?? {};
|
|
29946
|
-
this.selectionTouchedCache = state.selectionTouched ?? {};
|
|
29947
29910
|
this.gatewayEnabledCache = state.gatewayEnabled !== false;
|
|
29948
29911
|
this.providerOrderCache = state.providerOrder ?? [];
|
|
29949
29912
|
}
|
|
@@ -30021,7 +29984,6 @@ var AccountPool = class {
|
|
|
30021
29984
|
accounts: this.cache,
|
|
30022
29985
|
disabledModels: this.modelCache,
|
|
30023
29986
|
contextBudgets: this.contextBudgetCache,
|
|
30024
|
-
selectionTouched: this.selectionTouchedCache,
|
|
30025
29987
|
...this.lockFields(),
|
|
30026
29988
|
// ⚠️ 必须与账号、黑名单取自**同一时刻**的快照(见本方法的注释):
|
|
30027
29989
|
// 分两次快照会让同一份文档里的字段互相矛盾。
|
|
@@ -30143,9 +30105,6 @@ var AccountPool = class {
|
|
|
30143
30105
|
else delete perProvider[modelId];
|
|
30144
30106
|
if (Object.keys(perProvider).length === 0) delete next[provider];
|
|
30145
30107
|
else next[provider] = perProvider;
|
|
30146
|
-
// 先置位再落盘:`writeModels` 排队的快照里已带上标记,一次写入同时完成
|
|
30147
|
-
// 「改黑名单」和「记录用户动过选择」,不会出现只落了一半的状态。
|
|
30148
|
-
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30149
30108
|
await this.writeModels(next);
|
|
30150
30109
|
}
|
|
30151
30110
|
/**
|
|
@@ -30172,29 +30131,6 @@ var AccountPool = class {
|
|
|
30172
30131
|
this.ensureLoaded();
|
|
30173
30132
|
return { ...this.contextBudgetCache[provider] ?? {} };
|
|
30174
30133
|
}
|
|
30175
|
-
/**
|
|
30176
|
-
* 该 provider 的模型选择是否被用户动过。
|
|
30177
|
-
*
|
|
30178
|
-
* 启动补静音据此判断「要不要补」:动过就尊重用户的当前状态(哪怕是全开,
|
|
30179
|
-
* 表现为空黑名单),没动过才补静音。
|
|
30180
|
-
*/
|
|
30181
|
-
selectionTouched(provider) {
|
|
30182
|
-
this.ensureLoaded();
|
|
30183
|
-
return this.selectionTouchedCache[provider] === true;
|
|
30184
|
-
}
|
|
30185
|
-
/**
|
|
30186
|
-
* 置位「用户动过该 provider 的模型选择」并落盘。
|
|
30187
|
-
*
|
|
30188
|
-
* ⚠️ 幂等:已置位直接返回,不产生写入 —— 否则每次勾选都要重写整份文档。
|
|
30189
|
-
*/
|
|
30190
|
-
async touchSelection(provider) {
|
|
30191
|
-
this.ensureLoaded();
|
|
30192
|
-
if (this.selectionTouchedCache[provider] === true) return;
|
|
30193
|
-
const next = { ...this.selectionTouchedCache, [provider]: true };
|
|
30194
|
-
this.selectionTouchedCache = next;
|
|
30195
|
-
if (this.store.kind === "memory") return;
|
|
30196
|
-
await this.queueStoreSave();
|
|
30197
|
-
}
|
|
30198
30134
|
/**
|
|
30199
30135
|
* 设置/清除单个模型的上下文预算。
|
|
30200
30136
|
*
|
|
@@ -30224,18 +30160,13 @@ var AccountPool = class {
|
|
|
30224
30160
|
* 注意这与 {@link clearDisabledModels} **不对称** —— 后者的语义是「清空」,
|
|
30225
30161
|
* 即使传入空列表也仍有事可做(详见该方法注释)。
|
|
30226
30162
|
*/
|
|
30227
|
-
async setModelsDisabled(provider, modelIds
|
|
30163
|
+
async setModelsDisabled(provider, modelIds) {
|
|
30228
30164
|
if (modelIds.length === 0) return;
|
|
30229
30165
|
this.ensureLoaded();
|
|
30230
30166
|
const next = { ...this.modelCache };
|
|
30231
30167
|
const perProvider = { ...next[provider] ?? {} };
|
|
30232
30168
|
for (const id of modelIds) perProvider[id] = true;
|
|
30233
30169
|
next[provider] = perProvider;
|
|
30234
|
-
// @param options.mark - 默认 true(用户动作);自动静音传 false,见
|
|
30235
|
-
// touchSelection 的说明 —— 它不是「用户动过选择」。
|
|
30236
|
-
if (options?.mark !== false) {
|
|
30237
|
-
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30238
|
-
}
|
|
30239
30170
|
await this.writeModels(next);
|
|
30240
30171
|
}
|
|
30241
30172
|
/**
|
|
@@ -30268,9 +30199,6 @@ var AccountPool = class {
|
|
|
30268
30199
|
const next = { ...this.modelCache };
|
|
30269
30200
|
if (Object.keys(nextPerProvider).length === 0) delete next[provider];
|
|
30270
30201
|
else next[provider] = nextPerProvider;
|
|
30271
|
-
// 注意这条路径也会让黑名单变成空表(用户把该组全开),正是「全开」最
|
|
30272
|
-
// 容易发生的场景,必须同样置位,否则启动补静音会把它们再关回去。
|
|
30273
|
-
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30274
30202
|
await this.writeModels(next);
|
|
30275
30203
|
}
|
|
30276
30204
|
/**
|
|
@@ -30337,8 +30265,6 @@ var AccountPool = class {
|
|
|
30337
30265
|
if (this.modelCache[provider] === void 0) return;
|
|
30338
30266
|
const next = { ...this.modelCache };
|
|
30339
30267
|
delete next[provider];
|
|
30340
|
-
// 「全部打开」的最高频入口,必须置位(见 selectionTouched 注释)。
|
|
30341
|
-
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30342
30268
|
await this.writeModels(next);
|
|
30343
30269
|
}
|
|
30344
30270
|
/** 持久化模型黑名单(同时更新进程内权威副本)。 */
|
|
@@ -34786,37 +34712,6 @@ function contentToText2(content) {
|
|
|
34786
34712
|
return content.filter((block) => typeof block === "object" && block !== null && block.type === "text").map((block) => String(block.text)).join("");
|
|
34787
34713
|
}
|
|
34788
34714
|
var TOOL_RESULT_IMAGE_TEXT = "Attached image(s) from tool result:";
|
|
34789
|
-
/**
|
|
34790
|
-
* LOCAL ADAPTATION: a wire message that carries no payload at all.
|
|
34791
|
-
*
|
|
34792
|
-
* The domestic gateway rejects `{ role: "assistant", content: "" }` with
|
|
34793
|
-
* 400 / code 11151 "a message has empty content". That shape is reachable
|
|
34794
|
-
* without the model doing anything wrong: a turn interrupted while the model
|
|
34795
|
-
* was still thinking leaves an assistant message holding only reasoning, and
|
|
34796
|
-
* the serializers below emit it with an empty body because the domestic
|
|
34797
|
-
* gateway does not read `reasoning_content`. One such message then poisons
|
|
34798
|
-
* every later turn of that conversation, and retrying cannot help because the
|
|
34799
|
-
* offending message is history.
|
|
34800
|
-
*
|
|
34801
|
-
* `user` messages are deliberately NOT filtered: both gateways accept an empty
|
|
34802
|
-
* user message (verified against live endpoints), and dropping one would erase
|
|
34803
|
-
* a turn boundary the model is reading.
|
|
34804
|
-
*/
|
|
34805
|
-
function isBlankWireMessage(entry) {
|
|
34806
|
-
if (entry === null || typeof entry !== "object") return false;
|
|
34807
|
-
if (entry.role !== "assistant" && entry.role !== "system") return false;
|
|
34808
|
-
// A tool call IS content: it anchors the matching tool result, so removing
|
|
34809
|
-
// the message would orphan that result and fail differently.
|
|
34810
|
-
if (Array.isArray(entry.tool_calls) && entry.tool_calls.length > 0) return false;
|
|
34811
|
-
const content = entry.content;
|
|
34812
|
-
if (typeof content === "string") return content.trim().length === 0;
|
|
34813
|
-
if (Array.isArray(content)) return content.length === 0;
|
|
34814
|
-
return false;
|
|
34815
|
-
}
|
|
34816
|
-
function dropBlankWireMessages(wire) {
|
|
34817
|
-
const kept = wire.filter((entry) => !isBlankWireMessage(entry));
|
|
34818
|
-
return kept.length === wire.length ? wire : kept;
|
|
34819
|
-
}
|
|
34820
34715
|
function serializeMessages2(messages, imageUrls) {
|
|
34821
34716
|
const wire = [];
|
|
34822
34717
|
let pendingToolImages = [];
|
|
@@ -34895,7 +34790,7 @@ function serializeMessages2(messages, imageUrls) {
|
|
|
34895
34790
|
}
|
|
34896
34791
|
}
|
|
34897
34792
|
flushToolImages();
|
|
34898
|
-
return
|
|
34793
|
+
return wire;
|
|
34899
34794
|
}
|
|
34900
34795
|
function errorMessage2(error) {
|
|
34901
34796
|
if (error instanceof Error) return error.message;
|
|
@@ -38352,7 +38247,7 @@ function serializeMessages4(messages, imageUrls) {
|
|
|
38352
38247
|
}
|
|
38353
38248
|
}
|
|
38354
38249
|
flushToolImages();
|
|
38355
|
-
return
|
|
38250
|
+
return wire;
|
|
38356
38251
|
}
|
|
38357
38252
|
function errorMessage4(error) {
|
|
38358
38253
|
if (error instanceof Error) return error.message;
|
|
@@ -68282,19 +68177,12 @@ function apply(ctx, config = {}) {
|
|
|
68282
68177
|
all.map((account) => account.provider).filter((provider) => typeof provider === "string" && provider !== "" && !POOL_OWNED_PROVIDERS.has(provider))
|
|
68283
68178
|
);
|
|
68284
68179
|
for (const provider of providers) {
|
|
68285
|
-
// 「黑名单为空」有两种可能:用户从未选择过,或者用户把模型**全部打开**了
|
|
68286
|
-
// (打开时我们删除键,所以两者都是空表)。单看黑名单无法分辨,一律补静音
|
|
68287
|
-
// 就会把用户特意全开的渠道每次重启又关回去 —— 目录越小的渠道越必然中招。
|
|
68288
|
-
// 只有「用户没动过选择」才补。
|
|
68289
|
-
if (pool.selectionTouched(provider)) continue;
|
|
68290
68180
|
if (pool.disabledModelsFor(provider).size !== 0) continue;
|
|
68291
68181
|
const catalog = channelPackModelAdaptersRef?.[provider]?.listAllModels();
|
|
68292
68182
|
if (!Array.isArray(catalog) || catalog.length === 0) continue;
|
|
68293
|
-
|
|
68294
|
-
// 否则此后用户全开也会因为标记已在而被永久跳过后续逻辑。
|
|
68295
|
-
await pool.setModelsDisabled(provider, catalog.map((model) => model.id), { mark: false });
|
|
68183
|
+
await pool.setModelsDisabled(provider, catalog.map((model) => model.id));
|
|
68296
68184
|
ctx.logger.info(
|
|
68297
|
-
`[channel-pack] ${provider}
|
|
68185
|
+
`[channel-pack] ${provider} 启动补静音(已有账号但黑名单为空):已默认静音 ${catalog.length} 个模型(可在账号池页勾选启用)`
|
|
68298
68186
|
);
|
|
68299
68187
|
}
|
|
68300
68188
|
} catch (error) {
|
|
@@ -68308,19 +68196,15 @@ function apply(ctx, config = {}) {
|
|
|
68308
68196
|
const isFirstForProvider = pool.listAccountsByProvider(entry.provider).length === 0;
|
|
68309
68197
|
const hadEmptyBlocklist = pool.disabledModelsFor(entry.provider).size === 0;
|
|
68310
68198
|
const result = await rawAddAccount(entry);
|
|
68311
|
-
|
|
68312
|
-
// `selectionTouched` 判据,否则给已有账号的渠道新加一个号就会把用户刚打开的
|
|
68313
|
-
// 整目录模型全部关掉。
|
|
68314
|
-
const untouched = !pool.selectionTouched(entry.provider);
|
|
68315
|
-
if ((isFirstForProvider || hadEmptyBlocklist) && untouched && !POOL_OWNED_PROVIDERS.has(entry.provider)) {
|
|
68199
|
+
if ((isFirstForProvider || hadEmptyBlocklist) && !POOL_OWNED_PROVIDERS.has(entry.provider)) {
|
|
68316
68200
|
void Promise.resolve().then(async () => {
|
|
68317
68201
|
try {
|
|
68318
68202
|
const adapters = channelPackModelAdaptersRef;
|
|
68319
68203
|
const catalog = adapters?.[entry.provider]?.listAllModels();
|
|
68320
68204
|
if (!Array.isArray(catalog) || catalog.length === 0) return;
|
|
68321
|
-
await pool.setModelsDisabled(entry.provider, catalog.map((model) => model.id)
|
|
68205
|
+
await pool.setModelsDisabled(entry.provider, catalog.map((model) => model.id));
|
|
68322
68206
|
ctx.logger.info(
|
|
68323
|
-
`[channel-pack] ${entry.provider} ${isFirstForProvider ? "首次登录" : "
|
|
68207
|
+
`[channel-pack] ${entry.provider} ${isFirstForProvider ? "首次登录" : "补静音(黑名单为空)"}:已默认静音 ${catalog.length} 个模型(可在账号池页勾选启用)`
|
|
68324
68208
|
);
|
|
68325
68209
|
} catch (error) {
|
|
68326
68210
|
ctx.logger.warn(
|