dsh-xdpool 2.5.1 → 2.6.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 +169 -0
- package/README.en.md +10 -5
- package/README.md +10 -5
- package/assets/community-qr-qq.jpg +0 -0
- package/assets/community-qr-wechat.jpg +0 -0
- package/lib/bin.js +427 -74
- package/lib/client.js +1060 -235
- package/lib/index.d.ts +376 -61
- package/lib/index.js +2097 -937
- package/package.json +1 -1
- package/vendor/channel-pack/pack.js +346 -24
- package/assets/community-qr.png +0 -0
|
@@ -29453,6 +29453,15 @@ 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({}),
|
|
29456
29465
|
loomyPermanentLocked: Schema.boolean().default(false),
|
|
29457
29466
|
gatewayEnabled: Schema.boolean().default(true),
|
|
29458
29467
|
providerOrder: Schema.array(Schema.string()).default([])
|
|
@@ -29511,6 +29520,14 @@ function sanitizeContextBudgets(raw) {
|
|
|
29511
29520
|
}
|
|
29512
29521
|
return result;
|
|
29513
29522
|
}
|
|
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
|
+
}
|
|
29514
29531
|
function sanitizeAccounts(raw) {
|
|
29515
29532
|
if (!Array.isArray(raw)) return [];
|
|
29516
29533
|
const accounts = [];
|
|
@@ -29542,6 +29559,7 @@ var SettingsStore = class {
|
|
|
29542
29559
|
accounts: sanitizeAccounts(value.accounts),
|
|
29543
29560
|
disabledModels: sanitizeDisabledModels(value.disabledModels),
|
|
29544
29561
|
contextBudgets: sanitizeContextBudgets(value.contextBudgets),
|
|
29562
|
+
selectionTouched: sanitizeSelectionTouched(value.selectionTouched),
|
|
29545
29563
|
// 只是**镜像**(权威表在 permanent-locks.json);老文档没这个键 → false。
|
|
29546
29564
|
loomyPermanentLocked: value.loomyPermanentLocked === true,
|
|
29547
29565
|
// 老文档没这个键 → 启用,与升级前行为一致。
|
|
@@ -29557,6 +29575,9 @@ var SettingsStore = class {
|
|
|
29557
29575
|
// 预算表丢失的后果只是「下次选择器回到原生窗口」,用户重选一次即可
|
|
29558
29576
|
// (纯偏好、可逆),故接受老版本重写时丢掉本键。
|
|
29559
29577
|
contextBudgets: state.contextBudgets ?? {},
|
|
29578
|
+
// 丢了这个标记的后果是「下次启动把该渠道重新静音一次」,用户再勾一遍
|
|
29579
|
+
// 就会重新置位,属可逆,故与 contextBudgets 同样接受老版本重写时丢掉。
|
|
29580
|
+
selectionTouched: state.selectionTouched ?? {},
|
|
29560
29581
|
// 镜像字段由 AccountPool 与独立文档**同源写出**:同机上只认这个字段的
|
|
29561
29582
|
// 旧版本代码(其它 profile)读它、也会原样写回它,故两边不会脱节。
|
|
29562
29583
|
loomyPermanentLocked: state.loomyPermanentLocked === true,
|
|
@@ -29661,6 +29682,7 @@ var FileStore = class {
|
|
|
29661
29682
|
accounts: sanitizeAccounts(value.accounts),
|
|
29662
29683
|
disabledModels: sanitizeDisabledModels(value.disabledModels),
|
|
29663
29684
|
contextBudgets: sanitizeContextBudgets(value.contextBudgets),
|
|
29685
|
+
selectionTouched: sanitizeSelectionTouched(value.selectionTouched),
|
|
29664
29686
|
// 只是镜像(权威表在 permanent-locks.json);老文档没这个键 → false。
|
|
29665
29687
|
loomyPermanentLocked: value.loomyPermanentLocked === true,
|
|
29666
29688
|
// 老文档没这个键 → 启用,与升级前行为一致。
|
|
@@ -29705,6 +29727,9 @@ var FileStore = class {
|
|
|
29705
29727
|
accounts,
|
|
29706
29728
|
disabledModels: {},
|
|
29707
29729
|
contextBudgets: {},
|
|
29730
|
+
// 从凭据文件重建的文档不含任何选择历史 → 空表,让补静音照常生效
|
|
29731
|
+
// (这些账号确实是「从未选择过」,正是补静音要照顾的情形)。
|
|
29732
|
+
selectionTouched: {},
|
|
29708
29733
|
// 凭据文件里没有任何锁定信息 → 镜像写 false(权威表另有其文档)。
|
|
29709
29734
|
loomyPermanentLocked: false,
|
|
29710
29735
|
// 恢复出的文档本来就不含任何开关信息 → 启用(与全新安装一致)。
|
|
@@ -29812,6 +29837,82 @@ function createPermanentLockStore(ctx) {
|
|
|
29812
29837
|
|
|
29813
29838
|
// vendor/channel-pack/src/account-pool.ts
|
|
29814
29839
|
var EMPTY_MODEL_SET = /* @__PURE__ */ new Set();
|
|
29840
|
+
|
|
29841
|
+
// [xdpool-local] laneAllowedIds
|
|
29842
|
+
//
|
|
29843
|
+
// buddy / workbuddy are exempt from the startup silence pass because
|
|
29844
|
+
// dsh-xdpool's own pool card owns their model selection (see
|
|
29845
|
+
// POOL_OWNED_PROVIDERS below). The exemption avoided a conflict but not a GAP:
|
|
29846
|
+
// `disabledModelsFor("buddy")` is therefore always empty, so every adapter
|
|
29847
|
+
// returned its whole catalogue and the card's "1 of 16" had no effect on the
|
|
29848
|
+
// host's model picker (#121).
|
|
29849
|
+
//
|
|
29850
|
+
// This helper is the missing link. It intersects the pack's own blacklist with
|
|
29851
|
+
// the allow-list dsh-xdpool publishes on `globalThis.__xdpoolLaneSelection`,
|
|
29852
|
+
// keyed by lane id. The global is a handoff, not an API: the pack is a build
|
|
29853
|
+
// artifact of another repository and cannot import from the plugin's src/, and
|
|
29854
|
+
// threading a value through ten adapter constructors would be rejected by the
|
|
29855
|
+
// next pack refresh.
|
|
29856
|
+
//
|
|
29857
|
+
// Semantics that matter:
|
|
29858
|
+
// - an ABSENT entry (undefined) means "no restriction". A lane the user never
|
|
29859
|
+
// touched must keep showing everything; treating it as "empty allow-list"
|
|
29860
|
+
// would blank the picker.
|
|
29861
|
+
// - an EMPTY array means the user disabled every model, which is a real state
|
|
29862
|
+
// and must be honoured — hence the two are distinguished below rather than
|
|
29863
|
+
// folded with `??`.
|
|
29864
|
+
function laneAllowedIds(disabled, laneId, catalogue) {
|
|
29865
|
+
// [xdpool-local] laneAllowedIds returns the DISABLED set.
|
|
29866
|
+
//
|
|
29867
|
+
// The call sites consume the result as a BLACKLIST
|
|
29868
|
+
// (`source.filter(model => !disabled.has(model.id))`), so returning the
|
|
29869
|
+
// allow-list inverted the picker: ticking three of sixteen showed the other
|
|
29870
|
+
// thirteen. Building the complement needs the roster, which is what the third
|
|
29871
|
+
// argument carries.
|
|
29872
|
+
//
|
|
29873
|
+
// It is a THUNK: one adapter declares its roster on the line AFTER this call, so
|
|
29874
|
+
// a value would read it before initialization. The body runs later.
|
|
29875
|
+
const table = globalThis.__xdpoolLaneSelection;
|
|
29876
|
+
const allow = table === void 0 || table === null ? void 0 : table[laneId];
|
|
29877
|
+
if (allow === void 0) return disabled;
|
|
29878
|
+
const blocked = disabled === void 0 ? EMPTY_MODEL_SET : disabled;
|
|
29879
|
+
const allowed = new Set(allow);
|
|
29880
|
+
let rows;
|
|
29881
|
+
try {
|
|
29882
|
+
rows = typeof catalogue === "function" ? catalogue() : catalogue;
|
|
29883
|
+
} catch {
|
|
29884
|
+
// A roster that cannot be read yet leaves the pack's own blacklist in charge
|
|
29885
|
+
// rather than blanking the picker.
|
|
29886
|
+
return disabled;
|
|
29887
|
+
}
|
|
29888
|
+
if (!Array.isArray(rows) || rows.length === 0) return disabled;
|
|
29889
|
+
const out = new Set();
|
|
29890
|
+
for (const row of rows) {
|
|
29891
|
+
const id = row === null || row === void 0 ? void 0 : row.id;
|
|
29892
|
+
if (typeof id !== "string" || id === "") continue;
|
|
29893
|
+
// Disabled when the lane forbids it, or when the pack already did.
|
|
29894
|
+
if (!allowed.has(id) || blocked.has(id)) out.add(id);
|
|
29895
|
+
}
|
|
29896
|
+
// Everything allowed: hand back the pack's own gate, which is what `size === 0`
|
|
29897
|
+
// means to the caller.
|
|
29898
|
+
return out.size === 0 ? disabled : out;
|
|
29899
|
+
}
|
|
29900
|
+
|
|
29901
|
+
// [xdpool-local] LANE_DISPLAY_NAME
|
|
29902
|
+
//
|
|
29903
|
+
// The picker's group names for the two lanes this plugin owns, matching
|
|
29904
|
+
// `src/channels.ts` exactly. The vendored upstream constants still call the CN
|
|
29905
|
+
// lane "CodeBuddy (腾讯)", which is the endpoint's original product name; a user
|
|
29906
|
+
// who signed in to WorkBuddy saw that name in the model picker while every card
|
|
29907
|
+
// in this plugin called it "WorkBuddy 国内版" (#119).
|
|
29908
|
+
//
|
|
29909
|
+
// Scoped to `providerInfo` on purpose: `CODEBUDDY.displayName` also feeds the
|
|
29910
|
+
// rate-limit copy, and renaming the constant would rewrite messages about a
|
|
29911
|
+
// product the user never installed.
|
|
29912
|
+
const LANE_DISPLAY_NAME = {
|
|
29913
|
+
buddy: "WorkBuddy 国内版",
|
|
29914
|
+
workbuddy: "WorkBuddy 国际版"
|
|
29915
|
+
};
|
|
29815
29916
|
function allAccountsRateLimitedForModel(accounts, modelId, now = Date.now()) {
|
|
29816
29917
|
if (modelId.length === 0) return void 0;
|
|
29817
29918
|
let earliest;
|
|
@@ -29856,6 +29957,17 @@ var AccountPool = class {
|
|
|
29856
29957
|
* 把窗口收窄到 200K 这类值」,不改变上游真实能力。
|
|
29857
29958
|
*/
|
|
29858
29959
|
contextBudgetCache = {};
|
|
29960
|
+
/**
|
|
29961
|
+
* 「用户动过该 provider 的模型选择」标记的进程内权威副本。
|
|
29962
|
+
*
|
|
29963
|
+
* 存在的唯一理由是把「用户把模型全部打开」与「用户从未选择过」区分开:
|
|
29964
|
+
* 打开模型时黑名单里**删除**键(见 {@link setModelDisabled}),所以「全开」
|
|
29965
|
+
* 与「从未碰过」的 `disabledModels` 都是空表,单看黑名单无法分辨,启动补
|
|
29966
|
+
* 静音就会把「全开」当成「从未选择」再静音一次。
|
|
29967
|
+
*
|
|
29968
|
+
* ⚠️ 只记录已置位的 provider(缺键 = 从未选择)。
|
|
29969
|
+
*/
|
|
29970
|
+
selectionTouchedCache = {};
|
|
29859
29971
|
/**
|
|
29860
29972
|
* 「锁定永久积分」开关表的**权威进程内副本**(与 {@link cache} 同理)。
|
|
29861
29973
|
*
|
|
@@ -29907,6 +30019,7 @@ var AccountPool = class {
|
|
|
29907
30019
|
this.cache = state.accounts;
|
|
29908
30020
|
this.modelCache = state.disabledModels;
|
|
29909
30021
|
this.contextBudgetCache = state.contextBudgets ?? {};
|
|
30022
|
+
this.selectionTouchedCache = state.selectionTouched ?? {};
|
|
29910
30023
|
this.gatewayEnabledCache = state.gatewayEnabled !== false;
|
|
29911
30024
|
this.providerOrderCache = state.providerOrder ?? [];
|
|
29912
30025
|
}
|
|
@@ -29984,6 +30097,7 @@ var AccountPool = class {
|
|
|
29984
30097
|
accounts: this.cache,
|
|
29985
30098
|
disabledModels: this.modelCache,
|
|
29986
30099
|
contextBudgets: this.contextBudgetCache,
|
|
30100
|
+
selectionTouched: this.selectionTouchedCache,
|
|
29987
30101
|
...this.lockFields(),
|
|
29988
30102
|
// ⚠️ 必须与账号、黑名单取自**同一时刻**的快照(见本方法的注释):
|
|
29989
30103
|
// 分两次快照会让同一份文档里的字段互相矛盾。
|
|
@@ -30105,6 +30219,9 @@ var AccountPool = class {
|
|
|
30105
30219
|
else delete perProvider[modelId];
|
|
30106
30220
|
if (Object.keys(perProvider).length === 0) delete next[provider];
|
|
30107
30221
|
else next[provider] = perProvider;
|
|
30222
|
+
// 先置位再落盘:`writeModels` 排队的快照里已带上标记,一次写入同时完成
|
|
30223
|
+
// 「改黑名单」和「记录用户动过选择」,不会出现只落了一半的状态。
|
|
30224
|
+
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30108
30225
|
await this.writeModels(next);
|
|
30109
30226
|
}
|
|
30110
30227
|
/**
|
|
@@ -30131,6 +30248,29 @@ var AccountPool = class {
|
|
|
30131
30248
|
this.ensureLoaded();
|
|
30132
30249
|
return { ...this.contextBudgetCache[provider] ?? {} };
|
|
30133
30250
|
}
|
|
30251
|
+
/**
|
|
30252
|
+
* 该 provider 的模型选择是否被用户动过。
|
|
30253
|
+
*
|
|
30254
|
+
* 启动补静音据此判断「要不要补」:动过就尊重用户的当前状态(哪怕是全开,
|
|
30255
|
+
* 表现为空黑名单),没动过才补静音。
|
|
30256
|
+
*/
|
|
30257
|
+
selectionTouched(provider) {
|
|
30258
|
+
this.ensureLoaded();
|
|
30259
|
+
return this.selectionTouchedCache[provider] === true;
|
|
30260
|
+
}
|
|
30261
|
+
/**
|
|
30262
|
+
* 置位「用户动过该 provider 的模型选择」并落盘。
|
|
30263
|
+
*
|
|
30264
|
+
* ⚠️ 幂等:已置位直接返回,不产生写入 —— 否则每次勾选都要重写整份文档。
|
|
30265
|
+
*/
|
|
30266
|
+
async touchSelection(provider) {
|
|
30267
|
+
this.ensureLoaded();
|
|
30268
|
+
if (this.selectionTouchedCache[provider] === true) return;
|
|
30269
|
+
const next = { ...this.selectionTouchedCache, [provider]: true };
|
|
30270
|
+
this.selectionTouchedCache = next;
|
|
30271
|
+
if (this.store.kind === "memory") return;
|
|
30272
|
+
await this.queueStoreSave();
|
|
30273
|
+
}
|
|
30134
30274
|
/**
|
|
30135
30275
|
* 设置/清除单个模型的上下文预算。
|
|
30136
30276
|
*
|
|
@@ -30160,13 +30300,18 @@ var AccountPool = class {
|
|
|
30160
30300
|
* 注意这与 {@link clearDisabledModels} **不对称** —— 后者的语义是「清空」,
|
|
30161
30301
|
* 即使传入空列表也仍有事可做(详见该方法注释)。
|
|
30162
30302
|
*/
|
|
30163
|
-
async setModelsDisabled(provider, modelIds) {
|
|
30303
|
+
async setModelsDisabled(provider, modelIds, options) {
|
|
30164
30304
|
if (modelIds.length === 0) return;
|
|
30165
30305
|
this.ensureLoaded();
|
|
30166
30306
|
const next = { ...this.modelCache };
|
|
30167
30307
|
const perProvider = { ...next[provider] ?? {} };
|
|
30168
30308
|
for (const id of modelIds) perProvider[id] = true;
|
|
30169
30309
|
next[provider] = perProvider;
|
|
30310
|
+
// @param options.mark - 默认 true(用户动作);自动静音传 false,见
|
|
30311
|
+
// touchSelection 的说明 —— 它不是「用户动过选择」。
|
|
30312
|
+
if (options?.mark !== false) {
|
|
30313
|
+
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30314
|
+
}
|
|
30170
30315
|
await this.writeModels(next);
|
|
30171
30316
|
}
|
|
30172
30317
|
/**
|
|
@@ -30199,6 +30344,9 @@ var AccountPool = class {
|
|
|
30199
30344
|
const next = { ...this.modelCache };
|
|
30200
30345
|
if (Object.keys(nextPerProvider).length === 0) delete next[provider];
|
|
30201
30346
|
else next[provider] = nextPerProvider;
|
|
30347
|
+
// 注意这条路径也会让黑名单变成空表(用户把该组全开),正是「全开」最
|
|
30348
|
+
// 容易发生的场景,必须同样置位,否则启动补静音会把它们再关回去。
|
|
30349
|
+
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30202
30350
|
await this.writeModels(next);
|
|
30203
30351
|
}
|
|
30204
30352
|
/**
|
|
@@ -30265,6 +30413,8 @@ var AccountPool = class {
|
|
|
30265
30413
|
if (this.modelCache[provider] === void 0) return;
|
|
30266
30414
|
const next = { ...this.modelCache };
|
|
30267
30415
|
delete next[provider];
|
|
30416
|
+
// 「全部打开」的最高频入口,必须置位(见 selectionTouched 注释)。
|
|
30417
|
+
this.selectionTouchedCache = { ...this.selectionTouchedCache, [provider]: true };
|
|
30268
30418
|
await this.writeModels(next);
|
|
30269
30419
|
}
|
|
30270
30420
|
/** 持久化模型黑名单(同时更新进程内权威副本)。 */
|
|
@@ -32718,7 +32868,22 @@ function isQueueError(status, body) {
|
|
|
32718
32868
|
return status === 400 && (body.includes("TM.00001041") || /peak\s+usage|try\s+again\s+after|peak\s+hours/i.test(body) || /high\s+demand|too\s+many\s+requests/i.test(body));
|
|
32719
32869
|
}
|
|
32720
32870
|
function isAuthError(status, body) {
|
|
32721
|
-
|
|
32871
|
+
// [xdpool-local] 403 is not auth for CodeArts.
|
|
32872
|
+
//
|
|
32873
|
+
// Every IAM rejection this gateway produces is measured to be 401 APIG.0301:
|
|
32874
|
+
// bad SK signature, truncated STS token, wrong AK, and a body rewritten after
|
|
32875
|
+
// signing all answer 401. A rejected User-Agent answers 403 TM.00000001, which
|
|
32876
|
+
// is a WAF verdict on a request header and says nothing about the credential.
|
|
32877
|
+
//
|
|
32878
|
+
// 403 can still be about the token (`APIG.0602 security token has expired` is
|
|
32879
|
+
// real), so the body decides. An unrecognised 403 falls through to the non-auth
|
|
32880
|
+
// path, which is the honest answer: the upstream refused without claiming an
|
|
32881
|
+
// auth failure.
|
|
32882
|
+
if (status === 401) return true;
|
|
32883
|
+
if (status === 403) {
|
|
32884
|
+
return body.includes("APIG.0602")
|
|
32885
|
+
|| /invalid\s+token|token\s+expired|token\s+is\s+invalid|signature\s+fail|Unauthorized/i.test(body);
|
|
32886
|
+
}
|
|
32722
32887
|
return body.includes("APIG.0602") || /invalid\s+token|token\s+expired|token\s+is\s+invalid/i.test(body);
|
|
32723
32888
|
}
|
|
32724
32889
|
function isSseQueueErrorCode(code) {
|
|
@@ -32745,7 +32910,31 @@ function errorDetail(body) {
|
|
|
32745
32910
|
return body;
|
|
32746
32911
|
}
|
|
32747
32912
|
function httpErrorCode(status, body) {
|
|
32748
|
-
|
|
32913
|
+
// [xdpool-local] benefitNotFound — see tools/patch-pack-error-kinds.mjs
|
|
32914
|
+
//
|
|
32915
|
+
// CodeArts answers `InferHub.4004.200 benefit not found` when the account has
|
|
32916
|
+
// no entitlement for the model. The request carries `maas_type: benefit`
|
|
32917
|
+
// because the model is in CODEARTS_BENEFIT_FALLBACK, i.e. the plugin asked for
|
|
32918
|
+
// a benefit the ACCOUNT does not have — not a bad credential.
|
|
32919
|
+
//
|
|
32920
|
+
// Checked BEFORE the 401/403 → "AUTH" rule because that rule is what turned
|
|
32921
|
+
// this into "API 密钥无效" and sent users to re-authenticate over a permission
|
|
32922
|
+
// they simply do not hold (#118). Left as INVALID_REQUEST: terminal, not auth,
|
|
32923
|
+
// so the pool does not park a perfectly good account.
|
|
32924
|
+
{
|
|
32925
|
+
const probe = typeof body === "string" ? body : JSON.stringify(body ?? "");
|
|
32926
|
+
if (probe.includes("InferHub.4004.200") || probe.toLowerCase().includes("benefit not found")) {
|
|
32927
|
+
return "INVALID_REQUEST";
|
|
32928
|
+
}
|
|
32929
|
+
}
|
|
32930
|
+
// [xdpool-local] 403 is not auth — see isAuthError. The frontend renders AUTH
|
|
32931
|
+
// as "API 密钥无效", so routing a WAF 403 here produced a confident wrong
|
|
32932
|
+
// diagnosis. Only a 401, or a 403 whose body names the token, is AUTH.
|
|
32933
|
+
if (status === 401) return "AUTH";
|
|
32934
|
+
if (status === 403 && (String(body).includes("APIG.0602")
|
|
32935
|
+
|| /invalid\s+token|token\s+expired|token\s+is\s+invalid|signature\s+fail|Unauthorized/i.test(String(body)))) {
|
|
32936
|
+
return "AUTH";
|
|
32937
|
+
}
|
|
32749
32938
|
const detail = errorDetail(body);
|
|
32750
32939
|
if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE;
|
|
32751
32940
|
if (status === 429) return "RATE_LIMIT";
|
|
@@ -32753,6 +32942,9 @@ function httpErrorCode(status, body) {
|
|
|
32753
32942
|
if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE;
|
|
32754
32943
|
return "INVALID_REQUEST";
|
|
32755
32944
|
}
|
|
32945
|
+
// [xdpool-local] A 200 body can carry the benefit refusal too (the upstream
|
|
32946
|
+
// answers 200 with an error frame), so the probe above is the only guard that
|
|
32947
|
+
// covers both transports — nothing to do here.
|
|
32756
32948
|
if (status >= 500) return "SERVER";
|
|
32757
32949
|
return `HTTP_${status}`;
|
|
32758
32950
|
}
|
|
@@ -33177,6 +33369,17 @@ var CodeArtsAdapter = class extends LlmAdapter {
|
|
|
33177
33369
|
extraSignedHeaders
|
|
33178
33370
|
);
|
|
33179
33371
|
const headers = new Headers(attributionHeaders());
|
|
33372
|
+
// [xdpool-local] Override the host USER-AGENT, or the gateway answers 403.
|
|
33373
|
+
//
|
|
33374
|
+
// attributionHeaders() stamps the DSH bot UA and snap-access rejects it
|
|
33375
|
+
// outright with 403 {error_code:TM.00000001}. Measured against the live
|
|
33376
|
+
// gateway with the SAME credential, body and endpoint: the harness UA gets
|
|
33377
|
+
// 403, no UA gets 200, an IDE-shaped UA gets 200 — so the UA alone decides,
|
|
33378
|
+
// and every CodeArts request failed with a message that reads like a bad key.
|
|
33379
|
+
//
|
|
33380
|
+
// Only the UA is replaced. The other attribution headers stay: they identify
|
|
33381
|
+
// the host for support and are not rejected.
|
|
33382
|
+
headers.set("user-agent", "CodeArts-Agent/1.0 (Windows NT 10.0; Win64; x64)");
|
|
33180
33383
|
signed.forEach((value, key) => {
|
|
33181
33384
|
if (key !== "content-type") headers.set(key, value);
|
|
33182
33385
|
});
|
|
@@ -33265,6 +33468,28 @@ var CodeArtsAdapter = class extends LlmAdapter {
|
|
|
33265
33468
|
const errorText = await response.text().catch(() => "");
|
|
33266
33469
|
if (isAuthError(response.status, errorText) && !authRefreshed) {
|
|
33267
33470
|
authRefreshed = true;
|
|
33471
|
+
// [xdpool-local] auth failure rotates the account, not just refresh it.
|
|
33472
|
+
//
|
|
33473
|
+
// This branch used to refresh and continue, which re-reads the SAME
|
|
33474
|
+
// account — so with two accounts in the pool, a credential the gateway
|
|
33475
|
+
// rejects for the first one failed the whole request while the second
|
|
33476
|
+
// would have worked. Every other failure class already rotates via
|
|
33477
|
+
// accountPool.getAvailableAccount; auth was the one exception.
|
|
33478
|
+
//
|
|
33479
|
+
// One shot per account: `rateLimitTried` holds the ids this request has
|
|
33480
|
+
// already spent, so a pool of N accounts gets at most N-1 extra
|
|
33481
|
+
// attempts and the loop cannot spin.
|
|
33482
|
+
if (this.options.accountPool) {
|
|
33483
|
+
const next = await this.options.accountPool.getAvailableAccount(
|
|
33484
|
+
"codearts", options.model, rateLimitTried,
|
|
33485
|
+
);
|
|
33486
|
+
if (next && !rateLimitTried.has(next.entry.id)) {
|
|
33487
|
+
rateLimitTried.add(next.entry.id);
|
|
33488
|
+
credential = next.credential;
|
|
33489
|
+
currentAccountId = next.entry.id;
|
|
33490
|
+
continue;
|
|
33491
|
+
}
|
|
33492
|
+
}
|
|
33268
33493
|
await this.options.refresh();
|
|
33269
33494
|
credential = await this.options.resolveCredential();
|
|
33270
33495
|
if (credential === void 0 || !credential.access_key_id || !credential.secret_access_key || !credential.security_token) {
|
|
@@ -33326,7 +33551,20 @@ var CodeArtsAdapter = class extends LlmAdapter {
|
|
|
33326
33551
|
const probe = await this.queryQueueStatus(credential, options.model, options.signal);
|
|
33327
33552
|
if (probe === void 0 || probe.status === "working") {
|
|
33328
33553
|
const code = httpErrorCode(response.status, errorText);
|
|
33329
|
-
|
|
33554
|
+
// [xdpool-local] error detail on the HTTP-status throw.
|
|
33555
|
+
//
|
|
33556
|
+
// "HTTP 403" alone cannot be diagnosed: the 403 in this report was a
|
|
33557
|
+
// WAF verdict on the User-Agent, and with no body in the message the
|
|
33558
|
+
// operator went looking for a credential problem that did not exist.
|
|
33559
|
+
// The body names the cause, truncated so an HTML error page cannot
|
|
33560
|
+
// flood the message.
|
|
33561
|
+
const failureDetail = errorDetail(errorText).slice(0, 200);
|
|
33562
|
+
throw new LlmError2(
|
|
33563
|
+
`codearts: model request failed with HTTP ${response.status}`
|
|
33564
|
+
+ (failureDetail === "" ? "" : ` (${failureDetail})`),
|
|
33565
|
+
code,
|
|
33566
|
+
{ status: response.status },
|
|
33567
|
+
);
|
|
33330
33568
|
}
|
|
33331
33569
|
}
|
|
33332
33570
|
}
|
|
@@ -34712,6 +34950,37 @@ function contentToText2(content) {
|
|
|
34712
34950
|
return content.filter((block) => typeof block === "object" && block !== null && block.type === "text").map((block) => String(block.text)).join("");
|
|
34713
34951
|
}
|
|
34714
34952
|
var TOOL_RESULT_IMAGE_TEXT = "Attached image(s) from tool result:";
|
|
34953
|
+
/**
|
|
34954
|
+
* LOCAL ADAPTATION: a wire message that carries no payload at all.
|
|
34955
|
+
*
|
|
34956
|
+
* The domestic gateway rejects `{ role: "assistant", content: "" }` with
|
|
34957
|
+
* 400 / code 11151 "a message has empty content". That shape is reachable
|
|
34958
|
+
* without the model doing anything wrong: a turn interrupted while the model
|
|
34959
|
+
* was still thinking leaves an assistant message holding only reasoning, and
|
|
34960
|
+
* the serializers below emit it with an empty body because the domestic
|
|
34961
|
+
* gateway does not read `reasoning_content`. One such message then poisons
|
|
34962
|
+
* every later turn of that conversation, and retrying cannot help because the
|
|
34963
|
+
* offending message is history.
|
|
34964
|
+
*
|
|
34965
|
+
* `user` messages are deliberately NOT filtered: both gateways accept an empty
|
|
34966
|
+
* user message (verified against live endpoints), and dropping one would erase
|
|
34967
|
+
* a turn boundary the model is reading.
|
|
34968
|
+
*/
|
|
34969
|
+
function isBlankWireMessage(entry) {
|
|
34970
|
+
if (entry === null || typeof entry !== "object") return false;
|
|
34971
|
+
if (entry.role !== "assistant" && entry.role !== "system") return false;
|
|
34972
|
+
// A tool call IS content: it anchors the matching tool result, so removing
|
|
34973
|
+
// the message would orphan that result and fail differently.
|
|
34974
|
+
if (Array.isArray(entry.tool_calls) && entry.tool_calls.length > 0) return false;
|
|
34975
|
+
const content = entry.content;
|
|
34976
|
+
if (typeof content === "string") return content.trim().length === 0;
|
|
34977
|
+
if (Array.isArray(content)) return content.length === 0;
|
|
34978
|
+
return false;
|
|
34979
|
+
}
|
|
34980
|
+
function dropBlankWireMessages(wire) {
|
|
34981
|
+
const kept = wire.filter((entry) => !isBlankWireMessage(entry));
|
|
34982
|
+
return kept.length === wire.length ? wire : kept;
|
|
34983
|
+
}
|
|
34715
34984
|
function serializeMessages2(messages, imageUrls) {
|
|
34716
34985
|
const wire = [];
|
|
34717
34986
|
let pendingToolImages = [];
|
|
@@ -34790,7 +35059,7 @@ function serializeMessages2(messages, imageUrls) {
|
|
|
34790
35059
|
}
|
|
34791
35060
|
}
|
|
34792
35061
|
flushToolImages();
|
|
34793
|
-
return wire;
|
|
35062
|
+
return dropBlankWireMessages(wire);
|
|
34794
35063
|
}
|
|
34795
35064
|
function errorMessage2(error) {
|
|
34796
35065
|
if (error instanceof Error) return error.message;
|
|
@@ -35023,7 +35292,8 @@ var BuddyAdapter = class _BuddyAdapter extends LlmAdapter2 {
|
|
|
35023
35292
|
*/
|
|
35024
35293
|
providerInfo(provider) {
|
|
35025
35294
|
const id = typeof provider === "string" && provider.length > 0 ? provider : this.product.id;
|
|
35026
|
-
|
|
35295
|
+
// [xdpool-local] The picker's group name follows this plugin's own naming.
|
|
35296
|
+
return { id, name: LANE_DISPLAY_NAME[id] ?? this.product.displayName };
|
|
35027
35297
|
}
|
|
35028
35298
|
/**
|
|
35029
35299
|
* 模型列表:优先使用 /v3/config 动态拉取的远端列表,否则回退静态默认。
|
|
@@ -35193,7 +35463,7 @@ var BuddyAdapter = class _BuddyAdapter extends LlmAdapter2 {
|
|
|
35193
35463
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
35194
35464
|
await this.ensureRemoteModels();
|
|
35195
35465
|
const source = this.remoteModels ?? this.staticFallbackModels();
|
|
35196
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
35466
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => source);
|
|
35197
35467
|
const listed = disabled === void 0 || disabled.size === 0 ? source : source.filter((model) => !disabled.has(model.id));
|
|
35198
35468
|
return listed.map((model) => ({
|
|
35199
35469
|
provider: this.product.id,
|
|
@@ -35350,7 +35620,26 @@ var BuddyAdapter = class _BuddyAdapter extends LlmAdapter2 {
|
|
|
35350
35620
|
// its text rather than for claiming another client (measured 2026-10-28).
|
|
35351
35621
|
for (const message of messages) {
|
|
35352
35622
|
if ((message.role === "system" || message.role === "assistant") && typeof message.content === "string") {
|
|
35353
|
-
message.content = message.content.replace(/powered by DeepSeek Harness/gi, "running on DeepSeek Harness").replace(/DeepSeek Harness Web GUI/gi, "DeepSeek Harness web interface").replace(/Relative paths resolve from the workspace root/gi, "Relative paths are resolved against the workspace root")
|
|
35623
|
+
message.content = message.content.replace(/powered by DeepSeek Harness/gi, "running on DeepSeek Harness").replace(/DeepSeek Harness Web GUI/gi, "DeepSeek Harness web interface").replace(/Relative paths resolve from the workspace root/gi, "Relative paths are resolved against the workspace root")
|
|
35624
|
+
// [xdpool-local] codexCliBanner — see tools/patch-pack-codex-banner.mjs
|
|
35625
|
+
//
|
|
35626
|
+
// Codex CLI 0.166 opens with a developer message the gate catalogued.
|
|
35627
|
+
// Measured (#112): the banner's first 184 chars pass, 185 (the same
|
|
35628
|
+
// text plus its newline) fail with 11128, and the same sentences sent
|
|
35629
|
+
// as three separate messages all pass — so the gate compares the first
|
|
35630
|
+
// PARAGRAPH as a unit, not a keyword list and not a length.
|
|
35631
|
+
//
|
|
35632
|
+
// Rewriting the opening CLAUSE is therefore enough, and it is the only
|
|
35633
|
+
// honest option: the rest of the 20 KB banner is ordinary prompt text
|
|
35634
|
+
// the model still needs. Meaning is preserved on purpose — this string
|
|
35635
|
+
// is also what the model reads, and mangling it to dodge a filter
|
|
35636
|
+
// trades a loud failure for a quiet one.
|
|
35637
|
+
//
|
|
35638
|
+
// The two spellings below are the ones Codex emits across its recent
|
|
35639
|
+
// releases; each keeps its own length and sentence shape.
|
|
35640
|
+
.replace(/You are a coding agent running in the Codex CLI, a terminal-based coding assistant/gi, "You are a coding agent running in this environment, a terminal-based coding assistant")
|
|
35641
|
+
.replace(/You are a coding agent running in the Codex CLI/gi, "You are a coding agent running in this environment")
|
|
35642
|
+
.replace(/Codex CLI is an open source project led by OpenAI/gi, "This coding agent is an open source project");
|
|
35354
35643
|
}
|
|
35355
35644
|
}
|
|
35356
35645
|
const tools = options.tools?.map((tool) => ({
|
|
@@ -36694,7 +36983,7 @@ var LobsteraiAdapter = class extends LlmAdapter3 {
|
|
|
36694
36983
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
36695
36984
|
await this.ensureRemoteModels();
|
|
36696
36985
|
const source = this.remoteModels ?? this.staticFallbackModels();
|
|
36697
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
36986
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => source);
|
|
36698
36987
|
const listed = disabled === void 0 || disabled.size === 0 ? source : source.filter((model) => !disabled.has(model.id));
|
|
36699
36988
|
return listed.map((model) => ({
|
|
36700
36989
|
provider: this.product.id,
|
|
@@ -37777,14 +38066,36 @@ function unwrapQoderEnvelopeStream(response, label) {
|
|
|
37777
38066
|
if (classifyInner(inner) === "error") {
|
|
37778
38067
|
let code;
|
|
37779
38068
|
let message2 = inner;
|
|
38069
|
+
// [xdpool-local] benefitNotFound / pricingUrl — see
|
|
38070
|
+
// tools/patch-pack-error-kinds.mjs
|
|
38071
|
+
//
|
|
38072
|
+
// A refusal whose body is only a pricingUrl is upstream pointing at its
|
|
38073
|
+
// own pricing page: a QUOTA signal, not a generic model error. The old
|
|
38074
|
+
// fallback (message2 = the whole inner JSON) showed the user a bare URL
|
|
38075
|
+
// next to "(112/model_error)", saying nothing about quota (#118). The
|
|
38076
|
+
// URL moves to detail so it stays diagnosable.
|
|
38077
|
+
let pricingUrl;
|
|
38078
|
+
let hasMessage = false;
|
|
38079
|
+
try {
|
|
38080
|
+
const probe = JSON.parse(inner);
|
|
38081
|
+
if (typeof probe?.pricingUrl === "string") pricingUrl = probe.pricingUrl;
|
|
38082
|
+
hasMessage = typeof probe?.message === "string";
|
|
38083
|
+
} catch {
|
|
38084
|
+
}
|
|
37780
38085
|
try {
|
|
37781
38086
|
const parsed = JSON.parse(inner);
|
|
37782
38087
|
if (parsed.code !== void 0) code = parsed.code;
|
|
37783
38088
|
if (typeof parsed.message === "string") message2 = parsed.message;
|
|
37784
38089
|
} catch {
|
|
37785
38090
|
}
|
|
38091
|
+
const quotaRefusal = pricingUrl !== void 0 && !hasMessage;
|
|
37786
38092
|
controller.enqueue(
|
|
37787
|
-
encoder2.encode(`data: ${JSON.stringify({
|
|
38093
|
+
encoder2.encode(`data: ${JSON.stringify(quotaRefusal ? {
|
|
38094
|
+
code: QUOTA_EXCEEDED_CODE,
|
|
38095
|
+
message: "qoder: out of quota — upstream pointed at its pricing page",
|
|
38096
|
+
detail: pricingUrl,
|
|
38097
|
+
type: "model_error"
|
|
38098
|
+
} : {
|
|
37788
38099
|
...code === void 0 ? {} : { code },
|
|
37789
38100
|
message: message2,
|
|
37790
38101
|
type: "model_error"
|
|
@@ -38247,7 +38558,7 @@ function serializeMessages4(messages, imageUrls) {
|
|
|
38247
38558
|
}
|
|
38248
38559
|
}
|
|
38249
38560
|
flushToolImages();
|
|
38250
|
-
return wire;
|
|
38561
|
+
return dropBlankWireMessages(wire);
|
|
38251
38562
|
}
|
|
38252
38563
|
function errorMessage4(error) {
|
|
38253
38564
|
if (error instanceof Error) return error.message;
|
|
@@ -38799,8 +39110,8 @@ var QoderAdapter = class extends LlmAdapter4 {
|
|
|
38799
39110
|
}
|
|
38800
39111
|
async listModels(_provider) {
|
|
38801
39112
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
38802
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
38803
39113
|
const source = this.product.fallbackModels;
|
|
39114
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => source);
|
|
38804
39115
|
const listed = disabled === void 0 || disabled.size === 0 ? source : source.filter((model) => !disabled.has(model.id));
|
|
38805
39116
|
return listed.map((model) => ({
|
|
38806
39117
|
provider: this.product.id,
|
|
@@ -40489,7 +40800,7 @@ var TraeAdapter = class extends LlmAdapter5 {
|
|
|
40489
40800
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
40490
40801
|
await this.ensureRemoteModels();
|
|
40491
40802
|
const source = this.listAllModels();
|
|
40492
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
40803
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => source);
|
|
40493
40804
|
const listed = disabled === void 0 || disabled.size === 0 ? source : source.filter((model) => !disabled.has(model.id));
|
|
40494
40805
|
return listed.map((model) => ({
|
|
40495
40806
|
provider: this.product.id,
|
|
@@ -41918,7 +42229,7 @@ var ClineAdapter = class extends LlmAdapter6 {
|
|
|
41918
42229
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
41919
42230
|
await this.ensureRemoteModels();
|
|
41920
42231
|
const source = this.remoteModels ?? this.fallbackCatalog();
|
|
41921
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
42232
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => source);
|
|
41922
42233
|
const listed = disabled === void 0 || disabled.size === 0 ? source : source.filter((model) => !disabled.has(model.id));
|
|
41923
42234
|
return listed.map((model) => ({
|
|
41924
42235
|
provider: this.product.id,
|
|
@@ -42514,7 +42825,7 @@ var LoomyAdapter = class extends LlmAdapter7 {
|
|
|
42514
42825
|
async listModels(_provider) {
|
|
42515
42826
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
42516
42827
|
const all = await this.loadModels();
|
|
42517
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
42828
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => all);
|
|
42518
42829
|
const listed = disabled === void 0 || disabled.size === 0 ? all : all.filter((model) => !disabled.has(model.id));
|
|
42519
42830
|
return listed.map((model) => ({
|
|
42520
42831
|
provider: this.product.id,
|
|
@@ -42970,7 +43281,7 @@ var RaccoonAdapter = class extends LlmAdapter8 {
|
|
|
42970
43281
|
async listModels(_provider) {
|
|
42971
43282
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
42972
43283
|
const all = await this.loadModels();
|
|
42973
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
43284
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => all);
|
|
42974
43285
|
const listed = disabled === void 0 || disabled.size === 0 ? all : all.filter((model) => !disabled.has(model.id));
|
|
42975
43286
|
return listed.map((model) => ({
|
|
42976
43287
|
provider: this.product.id,
|
|
@@ -54725,7 +55036,7 @@ var ZcodeAdapter = class extends LlmAdapter9 {
|
|
|
54725
55036
|
if (!await this.ready()) return [];
|
|
54726
55037
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
54727
55038
|
const all = await this.loadModels();
|
|
54728
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
55039
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => all);
|
|
54729
55040
|
const listed = disabled === void 0 || disabled.size === 0 ? all : all.filter((model) => !disabled.has(model.id));
|
|
54730
55041
|
return listed.map((model) => ({
|
|
54731
55042
|
provider: this.product.id,
|
|
@@ -58519,7 +58830,7 @@ var GeminiAdapter = class extends LlmAdapter10 {
|
|
|
58519
58830
|
async listModels(_provider) {
|
|
58520
58831
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
58521
58832
|
const all = this.loadModels();
|
|
58522
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
58833
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => all);
|
|
58523
58834
|
const listed = disabled === void 0 || disabled.size === 0 ? all : all.filter((model) => !disabled.has(model.id));
|
|
58524
58835
|
return listed.map((model) => ({
|
|
58525
58836
|
provider: this.product.id,
|
|
@@ -66314,7 +66625,7 @@ var MinimaxAdapter = class extends LlmAdapter11 {
|
|
|
66314
66625
|
async listModels(_provider) {
|
|
66315
66626
|
if (!await providerCatalogVisible(this.options.accountPool, this.product.id)) return [];
|
|
66316
66627
|
const all = await this.loadModels();
|
|
66317
|
-
const disabled = this.options.accountPool?.disabledModelsFor(this.product.id);
|
|
66628
|
+
const disabled = laneAllowedIds(this.options.accountPool?.disabledModelsFor(this.product.id), this.product.id, () => all);
|
|
66318
66629
|
const listed = disabled === void 0 || disabled.size === 0 ? all : all.filter((model) => !disabled.has(model.id));
|
|
66319
66630
|
return listed.map((model) => ({
|
|
66320
66631
|
provider: this.product.id,
|
|
@@ -68177,12 +68488,19 @@ function apply(ctx, config = {}) {
|
|
|
68177
68488
|
all.map((account) => account.provider).filter((provider) => typeof provider === "string" && provider !== "" && !POOL_OWNED_PROVIDERS.has(provider))
|
|
68178
68489
|
);
|
|
68179
68490
|
for (const provider of providers) {
|
|
68491
|
+
// 「黑名单为空」有两种可能:用户从未选择过,或者用户把模型**全部打开**了
|
|
68492
|
+
// (打开时我们删除键,所以两者都是空表)。单看黑名单无法分辨,一律补静音
|
|
68493
|
+
// 就会把用户特意全开的渠道每次重启又关回去 —— 目录越小的渠道越必然中招。
|
|
68494
|
+
// 只有「用户没动过选择」才补。
|
|
68495
|
+
if (pool.selectionTouched(provider)) continue;
|
|
68180
68496
|
if (pool.disabledModelsFor(provider).size !== 0) continue;
|
|
68181
68497
|
const catalog = channelPackModelAdaptersRef?.[provider]?.listAllModels();
|
|
68182
68498
|
if (!Array.isArray(catalog) || catalog.length === 0) continue;
|
|
68183
|
-
|
|
68499
|
+
// mark: false —— 自动静音不是「用户动过选择」,不能让它把标记置位,
|
|
68500
|
+
// 否则此后用户全开也会因为标记已在而被永久跳过后续逻辑。
|
|
68501
|
+
await pool.setModelsDisabled(provider, catalog.map((model) => model.id), { mark: false });
|
|
68184
68502
|
ctx.logger.info(
|
|
68185
|
-
`[channel-pack] ${provider}
|
|
68503
|
+
`[channel-pack] ${provider} 启动补静音(已有账号但从未选择过模型):已默认静音 ${catalog.length} 个模型(可在账号池页勾选启用)`
|
|
68186
68504
|
);
|
|
68187
68505
|
}
|
|
68188
68506
|
} catch (error) {
|
|
@@ -68196,15 +68514,19 @@ function apply(ctx, config = {}) {
|
|
|
68196
68514
|
const isFirstForProvider = pool.listAccountsByProvider(entry.provider).length === 0;
|
|
68197
68515
|
const hadEmptyBlocklist = pool.disabledModelsFor(entry.provider).size === 0;
|
|
68198
68516
|
const result = await rawAddAccount(entry);
|
|
68199
|
-
|
|
68517
|
+
// 同上:`hadEmptyBlocklist` 分不清「从未选择」与「用户全开」,再加一道
|
|
68518
|
+
// `selectionTouched` 判据,否则给已有账号的渠道新加一个号就会把用户刚打开的
|
|
68519
|
+
// 整目录模型全部关掉。
|
|
68520
|
+
const untouched = !pool.selectionTouched(entry.provider);
|
|
68521
|
+
if ((isFirstForProvider || hadEmptyBlocklist) && untouched && !POOL_OWNED_PROVIDERS.has(entry.provider)) {
|
|
68200
68522
|
void Promise.resolve().then(async () => {
|
|
68201
68523
|
try {
|
|
68202
68524
|
const adapters = channelPackModelAdaptersRef;
|
|
68203
68525
|
const catalog = adapters?.[entry.provider]?.listAllModels();
|
|
68204
68526
|
if (!Array.isArray(catalog) || catalog.length === 0) return;
|
|
68205
|
-
await pool.setModelsDisabled(entry.provider, catalog.map((model) => model.id));
|
|
68527
|
+
await pool.setModelsDisabled(entry.provider, catalog.map((model) => model.id), { mark: false });
|
|
68206
68528
|
ctx.logger.info(
|
|
68207
|
-
`[channel-pack] ${entry.provider} ${isFirstForProvider ? "首次登录" : "
|
|
68529
|
+
`[channel-pack] ${entry.provider} ${isFirstForProvider ? "首次登录" : "补静音(从未选择过)"}:已默认静音 ${catalog.length} 个模型(可在账号池页勾选启用)`
|
|
68208
68530
|
);
|
|
68209
68531
|
} catch (error) {
|
|
68210
68532
|
ctx.logger.warn(
|
package/assets/community-qr.png
DELETED
|
Binary file
|