dsh-workbuddy-xdpool 1.7.2 → 1.7.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/CHANGELOG.md +37 -0
- package/lib/bin.js +234 -43
- package/lib/client.js +89 -15
- package/lib/index.d.ts +1033 -926
- package/lib/index.js +332 -58
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -12,6 +12,39 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
|
12
12
|
import { createServer } from "node:http";
|
|
13
13
|
import { Readable } from "node:stream";
|
|
14
14
|
//#region src/upstream.ts
|
|
15
|
+
/**
|
|
16
|
+
* Backoff between catalog-fetch attempts.
|
|
17
|
+
*
|
|
18
|
+
* Three retries after the first try, so a startup network hiccup does not cost
|
|
19
|
+
* the user their model list for the whole session. The window covers the case
|
|
20
|
+
* that actually bit: several independent components reported `fetch failed`
|
|
21
|
+
* within the same second while the machine was still bringing its network up,
|
|
22
|
+
* and the very next request succeeded 0.7s later.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately short and bounded. This runs during plugin startup, so a long
|
|
25
|
+
* ladder would delay the provider appearing at all; the total added latency
|
|
26
|
+
* here is ~12s in the worst case, and only when the network is genuinely down.
|
|
27
|
+
*/
|
|
28
|
+
const CATALOG_RETRY_BACKOFF_MS = [
|
|
29
|
+
1e3,
|
|
30
|
+
3e3,
|
|
31
|
+
8e3
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Sleep helper for the retry ladder.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately NOT unref'd. An unref'd timer does not keep the event loop
|
|
37
|
+
* alive, so awaiting one can hang forever when nothing else is pending — the
|
|
38
|
+
* retry would simply never resume. That is exactly what the first version of
|
|
39
|
+
* this did, and it wedged the very first end-to-end run: `fetchModels` never
|
|
40
|
+
* settled. The wait is at most a few seconds and only happens on failure, so
|
|
41
|
+
* holding the loop open is the correct trade.
|
|
42
|
+
*/
|
|
43
|
+
function delay(ms) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
setTimeout(resolve, ms);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
15
48
|
/** CN chat base per dingminhua's on-machine probe (HTTP 200 for chat/models). */
|
|
16
49
|
const CN_CHAT_BASE = "https://copilot.tencent.com";
|
|
17
50
|
/** Billing/console base — `www.codebuddy.cn` is the billing origin. */
|
|
@@ -33,6 +66,13 @@ const MODELS_CATALOG_PATH = "/v2/enterprises/personal/models";
|
|
|
33
66
|
const GLOBAL_CONFIG_PATH = "/v3/config";
|
|
34
67
|
const JSON_TIMEOUT_MS = 3e4;
|
|
35
68
|
const ERROR_BODY_LIMIT = 4096;
|
|
69
|
+
/** True for a request the CALLER aborted, which must never be retried. */
|
|
70
|
+
function isAbortError(error) {
|
|
71
|
+
if (typeof error !== "object" || error === null) return false;
|
|
72
|
+
if (error.name === "AbortError") return true;
|
|
73
|
+
const cause = error.cause;
|
|
74
|
+
return typeof cause === "object" && cause !== null && cause.name === "AbortError";
|
|
75
|
+
}
|
|
36
76
|
/** Cap on the reassembled compaction reply, guarding against a runaway stream. */
|
|
37
77
|
const COMPLETION_TEXT_LIMIT = 65536;
|
|
38
78
|
/** Insufficient-credit markers, ASCII lowercase plus the original Chinese. */
|
|
@@ -745,9 +785,20 @@ function parseTask(value) {
|
|
|
745
785
|
var WorkBuddyUpstreamClient = class {
|
|
746
786
|
fetchImpl;
|
|
747
787
|
clientVersion;
|
|
788
|
+
/** Backoff ladder for `fetchModels`; empty means a single attempt. */
|
|
789
|
+
catalogRetryBackoffMs;
|
|
790
|
+
/**
|
|
791
|
+
* Optional logger for retry notices.
|
|
792
|
+
*
|
|
793
|
+
* Set by the host so a retry is visible in the log with its attempt count —
|
|
794
|
+
* without it, a retry that eventually succeeds is invisible, and an operator
|
|
795
|
+
* debugging "why was the catalog slow" has nothing to look at.
|
|
796
|
+
*/
|
|
797
|
+
logger;
|
|
748
798
|
constructor(options = {}) {
|
|
749
799
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
750
800
|
this.clientVersion = options.clientVersion ?? "2.0.4";
|
|
801
|
+
this.catalogRetryBackoffMs = options.catalogRetryBackoffMs ?? CATALOG_RETRY_BACKOFF_MS;
|
|
751
802
|
}
|
|
752
803
|
/**
|
|
753
804
|
* Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
|
|
@@ -904,6 +955,30 @@ var WorkBuddyUpstreamClient = class {
|
|
|
904
955
|
* below is common to the two branches.
|
|
905
956
|
*/
|
|
906
957
|
async fetchModels(credential, signal) {
|
|
958
|
+
const attempts = this.catalogRetryBackoffMs.length + 1;
|
|
959
|
+
let lastError;
|
|
960
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) try {
|
|
961
|
+
return await this.fetchModelsOnce(credential, signal);
|
|
962
|
+
} catch (error) {
|
|
963
|
+
lastError = error;
|
|
964
|
+
if (isAbortError(error)) throw error;
|
|
965
|
+
const wait = this.catalogRetryBackoffMs[attempt];
|
|
966
|
+
if (wait === void 0) break;
|
|
967
|
+
this.logger?.warn?.(`dsh-workbuddy-xdpool: model catalog fetch failed (attempt ${attempt + 1}/${attempts}), retrying in ${wait}ms`, error);
|
|
968
|
+
await delay(wait);
|
|
969
|
+
}
|
|
970
|
+
throw lastError;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* One catalog attempt, without retries.
|
|
974
|
+
*
|
|
975
|
+
* Split out so {@link fetchModels} can retry it: the failure this guards
|
|
976
|
+
* against is a startup network hiccup, where several independent components
|
|
977
|
+
* see `fetch failed` inside the same second and the very next request
|
|
978
|
+
* succeeds — exactly the case a single attempt turns into "the user's model
|
|
979
|
+
* list is missing half its entries for the rest of the session".
|
|
980
|
+
*/
|
|
981
|
+
async fetchModelsOnce(credential, signal) {
|
|
907
982
|
const global = regionOf(credential.domain) === "global";
|
|
908
983
|
const url = global ? `${globalBase(credential)}${GLOBAL_CONFIG_PATH}` : `${chatBase(credential)}${MODELS_CATALOG_PATH}`;
|
|
909
984
|
const headers = global ? {
|
|
@@ -2986,38 +3061,97 @@ ${new Date(until).toISOString()}
|
|
|
2986
3061
|
* Static fallback used before the first live catalog fetch, and whenever the
|
|
2987
3062
|
* upstream cannot be reached.
|
|
2988
3063
|
*
|
|
2989
|
-
*
|
|
2990
|
-
*
|
|
2991
|
-
*
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
3064
|
+
* This table is the DOMESTIC (CN) roster, and it is kept in step with what
|
|
3065
|
+
* `copilot.tencent.com` actually advertises. It matters more than a "just in
|
|
3066
|
+
* case" list usually would: a failed startup fetch falls back to it, and the
|
|
3067
|
+
* user sees that as "half my models were deleted" — `deepseek-v4.1-flash`
|
|
3068
|
+
* and friends simply vanishing from the picker, with no visible explanation.
|
|
3069
|
+
*
|
|
3070
|
+
* Every row below was captured from the live endpoint, INCLUDING the window
|
|
3071
|
+
* sizes: the previous version carried a 32K window for `hy3` when the gateway
|
|
3072
|
+
* says 192K, and listed `kimi-k3` under an id the gateway no longer uses
|
|
3073
|
+
* (`kimi-k3-1`). A stale fallback is worse than a short one — it looks
|
|
3074
|
+
* authoritative while being wrong.
|
|
2995
3075
|
*
|
|
2996
3076
|
* `multiplier: 0` is the gateways' own spelling of "free" (`credits: "x0.00"`),
|
|
2997
3077
|
* which is what turns on the free badge.
|
|
2998
3078
|
*/
|
|
2999
3079
|
const FALLBACK_WORKBUDDY_MODELS = [
|
|
3080
|
+
{
|
|
3081
|
+
id: "auto",
|
|
3082
|
+
name: "Auto",
|
|
3083
|
+
contextWindow: 256e3,
|
|
3084
|
+
maxOutputTokens: 32e3,
|
|
3085
|
+
supportsImages: true
|
|
3086
|
+
},
|
|
3087
|
+
{
|
|
3088
|
+
id: "hy4-preview",
|
|
3089
|
+
name: "Hy4 preview",
|
|
3090
|
+
contextWindow: 1e6,
|
|
3091
|
+
maxOutputTokens: 64e3,
|
|
3092
|
+
supportsImages: true,
|
|
3093
|
+
multiplier: .29
|
|
3094
|
+
},
|
|
3095
|
+
{
|
|
3096
|
+
id: "hy3",
|
|
3097
|
+
name: "Hy3",
|
|
3098
|
+
contextWindow: 192e3,
|
|
3099
|
+
maxOutputTokens: 64e3,
|
|
3100
|
+
supportsImages: true,
|
|
3101
|
+
multiplier: 0
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
id: "hy3-x",
|
|
3105
|
+
name: "Hy3",
|
|
3106
|
+
contextWindow: 192e3,
|
|
3107
|
+
maxOutputTokens: 64e3,
|
|
3108
|
+
supportsImages: true,
|
|
3109
|
+
multiplier: .05
|
|
3110
|
+
},
|
|
3111
|
+
{
|
|
3112
|
+
id: "deepseek-v4.1-flash",
|
|
3113
|
+
name: "Deepseek-V4.1-Flash",
|
|
3114
|
+
contextWindow: 1e6,
|
|
3115
|
+
maxOutputTokens: 128e3,
|
|
3116
|
+
supportsImages: true,
|
|
3117
|
+
multiplier: .11
|
|
3118
|
+
},
|
|
3119
|
+
{
|
|
3120
|
+
id: "deepseek-v4-pro",
|
|
3121
|
+
name: "Deepseek-V4-Pro",
|
|
3122
|
+
contextWindow: 1e6,
|
|
3123
|
+
maxOutputTokens: 128e3,
|
|
3124
|
+
supportsImages: true,
|
|
3125
|
+
multiplier: .51
|
|
3126
|
+
},
|
|
3127
|
+
{
|
|
3128
|
+
id: "deepseek-v4-flash",
|
|
3129
|
+
name: "DeepSeek-V4-Flash",
|
|
3130
|
+
contextWindow: 1e6,
|
|
3131
|
+
maxOutputTokens: 128e3,
|
|
3132
|
+
supportsImages: true
|
|
3133
|
+
},
|
|
3000
3134
|
{
|
|
3001
3135
|
id: "glm-5.3",
|
|
3002
3136
|
name: "GLM-5.3",
|
|
3003
|
-
contextWindow:
|
|
3004
|
-
maxOutputTokens:
|
|
3137
|
+
contextWindow: 1e6,
|
|
3138
|
+
maxOutputTokens: 64e3,
|
|
3005
3139
|
supportsImages: true,
|
|
3006
3140
|
multiplier: .79
|
|
3007
3141
|
},
|
|
3008
3142
|
{
|
|
3009
3143
|
id: "glm-5.3-flash",
|
|
3010
3144
|
name: "GLM-5.3-Flash",
|
|
3011
|
-
contextWindow:
|
|
3012
|
-
maxOutputTokens:
|
|
3145
|
+
contextWindow: 1e6,
|
|
3146
|
+
maxOutputTokens: 131072,
|
|
3013
3147
|
supportsImages: true,
|
|
3014
3148
|
multiplier: .06
|
|
3015
3149
|
},
|
|
3016
3150
|
{
|
|
3017
3151
|
id: "glm-5.2",
|
|
3018
3152
|
name: "GLM-5.2",
|
|
3019
|
-
contextWindow:
|
|
3020
|
-
maxOutputTokens:
|
|
3153
|
+
contextWindow: 1e6,
|
|
3154
|
+
maxOutputTokens: 64e3,
|
|
3021
3155
|
supportsImages: true,
|
|
3022
3156
|
multiplier: .79
|
|
3023
3157
|
},
|
|
@@ -3025,53 +3159,57 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3025
3159
|
id: "glm-5.1",
|
|
3026
3160
|
name: "GLM-5.1",
|
|
3027
3161
|
contextWindow: 2e5,
|
|
3028
|
-
maxOutputTokens:
|
|
3029
|
-
supportsImages: false
|
|
3030
|
-
|
|
3031
|
-
{
|
|
3032
|
-
id: "deepseek-v4-pro",
|
|
3033
|
-
name: "DeepSeek-V4-Pro",
|
|
3034
|
-
contextWindow: 2e5,
|
|
3035
|
-
maxOutputTokens: 128e3,
|
|
3036
|
-
supportsImages: true
|
|
3162
|
+
maxOutputTokens: 48e3,
|
|
3163
|
+
supportsImages: false,
|
|
3164
|
+
multiplier: .79
|
|
3037
3165
|
},
|
|
3038
3166
|
{
|
|
3039
|
-
id: "
|
|
3040
|
-
name: "
|
|
3167
|
+
id: "glm-5v-turbo",
|
|
3168
|
+
name: "GLM-5v-Turbo",
|
|
3041
3169
|
contextWindow: 2e5,
|
|
3042
|
-
maxOutputTokens:
|
|
3043
|
-
supportsImages: true
|
|
3170
|
+
maxOutputTokens: 64e3,
|
|
3171
|
+
supportsImages: true,
|
|
3172
|
+
multiplier: .71
|
|
3044
3173
|
},
|
|
3045
3174
|
{
|
|
3046
|
-
id: "kimi-k3",
|
|
3175
|
+
id: "kimi-k3-1",
|
|
3047
3176
|
name: "Kimi-K3",
|
|
3048
|
-
contextWindow:
|
|
3049
|
-
maxOutputTokens:
|
|
3177
|
+
contextWindow: 1e6,
|
|
3178
|
+
maxOutputTokens: 32e3,
|
|
3050
3179
|
supportsImages: true,
|
|
3051
3180
|
multiplier: 1.62
|
|
3052
3181
|
},
|
|
3053
3182
|
{
|
|
3054
|
-
id: "
|
|
3055
|
-
name: "
|
|
3056
|
-
contextWindow:
|
|
3057
|
-
maxOutputTokens:
|
|
3058
|
-
supportsImages: true
|
|
3183
|
+
id: "kimi-k2.8-preview",
|
|
3184
|
+
name: "Kimi-K2.8-Preview",
|
|
3185
|
+
contextWindow: 1e6,
|
|
3186
|
+
maxOutputTokens: 64e3,
|
|
3187
|
+
supportsImages: true,
|
|
3188
|
+
multiplier: .77
|
|
3059
3189
|
},
|
|
3060
3190
|
{
|
|
3061
|
-
id: "
|
|
3062
|
-
name: "
|
|
3063
|
-
contextWindow:
|
|
3064
|
-
maxOutputTokens:
|
|
3191
|
+
id: "kimi-k2.7",
|
|
3192
|
+
name: "Kimi-K2.7-Code",
|
|
3193
|
+
contextWindow: 256e3,
|
|
3194
|
+
maxOutputTokens: 32e3,
|
|
3065
3195
|
supportsImages: true,
|
|
3066
|
-
multiplier:
|
|
3196
|
+
multiplier: .57
|
|
3067
3197
|
},
|
|
3068
3198
|
{
|
|
3069
|
-
id: "
|
|
3070
|
-
name: "
|
|
3071
|
-
contextWindow:
|
|
3072
|
-
maxOutputTokens:
|
|
3199
|
+
id: "kimi-k2.6",
|
|
3200
|
+
name: "Kimi-K2.6",
|
|
3201
|
+
contextWindow: 256e3,
|
|
3202
|
+
maxOutputTokens: 32e3,
|
|
3073
3203
|
supportsImages: true,
|
|
3074
|
-
multiplier: .
|
|
3204
|
+
multiplier: .52
|
|
3205
|
+
},
|
|
3206
|
+
{
|
|
3207
|
+
id: "minimax-m3",
|
|
3208
|
+
name: "MiniMax-M3",
|
|
3209
|
+
contextWindow: 512e3,
|
|
3210
|
+
maxOutputTokens: 64e3,
|
|
3211
|
+
supportsImages: true,
|
|
3212
|
+
multiplier: .25
|
|
3075
3213
|
}
|
|
3076
3214
|
];
|
|
3077
3215
|
/** Live catalog with a static fallback behind it. */
|
|
@@ -3080,6 +3218,19 @@ var WorkBuddyCatalog = class {
|
|
|
3080
3218
|
listeners = /* @__PURE__ */ new Set();
|
|
3081
3219
|
/** User's model selection. Empty object = follow the catalog unfiltered. */
|
|
3082
3220
|
selection = {};
|
|
3221
|
+
/**
|
|
3222
|
+
* Whether `models` came from the gateway or from the static table.
|
|
3223
|
+
*
|
|
3224
|
+
* Tracked so the card can SAY which it is showing. A failed fetch used to be
|
|
3225
|
+
* invisible on screen: the picker simply held fewer models than before, which
|
|
3226
|
+
* the user reasonably read as "the plugin deleted my models" rather than "the
|
|
3227
|
+
* network hiccuped at startup".
|
|
3228
|
+
*/
|
|
3229
|
+
source = "fallback";
|
|
3230
|
+
/** When the live list last landed. */
|
|
3231
|
+
lastUpdatedAt;
|
|
3232
|
+
/** Why the last fetch failed, for display. */
|
|
3233
|
+
lastError;
|
|
3083
3234
|
current() {
|
|
3084
3235
|
return this.models;
|
|
3085
3236
|
}
|
|
@@ -3114,6 +3265,7 @@ var WorkBuddyCatalog = class {
|
|
|
3114
3265
|
/** Restore the static fallback, e.g. when the upstream stops answering. */
|
|
3115
3266
|
reset() {
|
|
3116
3267
|
this.models = FALLBACK_WORKBUDDY_MODELS;
|
|
3268
|
+
this.source = "fallback";
|
|
3117
3269
|
this.notify();
|
|
3118
3270
|
}
|
|
3119
3271
|
/** Replace the user's selection; the adapter rebuilds from `visible()`. */
|
|
@@ -3125,6 +3277,31 @@ var WorkBuddyCatalog = class {
|
|
|
3125
3277
|
currentSelection() {
|
|
3126
3278
|
return this.selection;
|
|
3127
3279
|
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Whether this catalog is serving live data or the built-in table.
|
|
3282
|
+
*
|
|
3283
|
+
* `fallback` is not an error state, but it IS a degraded one: the user is
|
|
3284
|
+
* looking at a shorter roster than the gateway offers, so the card says so
|
|
3285
|
+
* and offers a retry instead of letting them wonder where the models went.
|
|
3286
|
+
*/
|
|
3287
|
+
currentSource() {
|
|
3288
|
+
return this.source;
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* When the live list last landed, if it ever did.
|
|
3292
|
+
*
|
|
3293
|
+
* Named `catalogUpdatedAt()` rather than `updatedAt()` because the class
|
|
3294
|
+
* already had an `updatedAt` member; two members of the same name is a
|
|
3295
|
+
* compile error, and the awkwardness is a useful signal that the concept is
|
|
3296
|
+
* "when THIS catalog was refreshed", not a generic timestamp.
|
|
3297
|
+
*/
|
|
3298
|
+
catalogUpdatedAt() {
|
|
3299
|
+
return this.lastUpdatedAt;
|
|
3300
|
+
}
|
|
3301
|
+
/** Why the last fetch failed, if it did. */
|
|
3302
|
+
lastFetchError() {
|
|
3303
|
+
return this.lastError;
|
|
3304
|
+
}
|
|
3128
3305
|
onChange(listener) {
|
|
3129
3306
|
this.listeners.add(listener);
|
|
3130
3307
|
return () => this.listeners.delete(listener);
|
|
@@ -3135,6 +3312,20 @@ var WorkBuddyCatalog = class {
|
|
|
3135
3312
|
/** Replace the catalog from the live upstream list; keeps the fallback if empty. */
|
|
3136
3313
|
updateFromUpstream(models) {
|
|
3137
3314
|
this.update(catalogFromUpstream(models));
|
|
3315
|
+
this.source = "live";
|
|
3316
|
+
this.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3317
|
+
this.lastError = void 0;
|
|
3318
|
+
}
|
|
3319
|
+
/**
|
|
3320
|
+
* Record that a fetch attempt failed, leaving the current list in place.
|
|
3321
|
+
*
|
|
3322
|
+
* The list is deliberately NOT reset here: a refresh that fails should keep
|
|
3323
|
+
* whatever working catalog is already loaded, rather than demoting a healthy
|
|
3324
|
+
* session to the static table because one retry ran out.
|
|
3325
|
+
*/
|
|
3326
|
+
noteFetchFailure(message) {
|
|
3327
|
+
this.lastError = message;
|
|
3328
|
+
this.notify();
|
|
3138
3329
|
}
|
|
3139
3330
|
notify() {
|
|
3140
3331
|
for (const listener of this.listeners) listener();
|
|
@@ -3778,6 +3969,16 @@ function expertChatEvents(expert, conversationId, requestId) {
|
|
|
3778
3969
|
const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
|
|
3779
3970
|
/** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
|
|
3780
3971
|
const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
|
|
3972
|
+
/**
|
|
3973
|
+
* Re-fetch the upstream model catalog for both regions.
|
|
3974
|
+
*
|
|
3975
|
+
* Separate from {@link POOL_RESCAN_PATH} because they answer different
|
|
3976
|
+
* questions, and conflating them misled users: "detect accounts again" only
|
|
3977
|
+
* re-read the desktop snapshots, so it could NOT recover a model list that had
|
|
3978
|
+
* fallen back to the static table after a failed startup fetch. The only way
|
|
3979
|
+
* out was restarting DSH.
|
|
3980
|
+
*/
|
|
3981
|
+
const POOL_CATALOG_REFRESH_PATH = "/plugins/dsh-workbuddy-xdpool/models/refresh";
|
|
3781
3982
|
/** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
|
|
3782
3983
|
const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
|
|
3783
3984
|
/** Plugin-owned daily check-in action endpoint (claim today's reward). */
|
|
@@ -5949,7 +6150,10 @@ async function poolWebStatus(deps, region = "cn") {
|
|
|
5949
6150
|
shim,
|
|
5950
6151
|
automation,
|
|
5951
6152
|
creditReserves: deps.pool.creditReservesInOrder(),
|
|
5952
|
-
ignored: deps.ignoredAccounts?.() ?? []
|
|
6153
|
+
ignored: deps.ignoredAccounts?.() ?? [],
|
|
6154
|
+
catalogSource: deps.catalogs[region].currentSource(),
|
|
6155
|
+
...deps.catalogs[region].catalogUpdatedAt() === void 0 ? {} : { catalogUpdatedAt: deps.catalogs[region].catalogUpdatedAt() },
|
|
6156
|
+
...deps.catalogs[region].lastFetchError() === void 0 ? {} : { catalogError: safeMessage(deps.catalogs[region].lastFetchError()) }
|
|
5953
6157
|
};
|
|
5954
6158
|
}
|
|
5955
6159
|
/**
|
|
@@ -5972,6 +6176,35 @@ function registerPoolStatusRoute(ctx, deps) {
|
|
|
5972
6176
|
}
|
|
5973
6177
|
}
|
|
5974
6178
|
});
|
|
6179
|
+
/**
|
|
6180
|
+
* Re-fetch the upstream model catalog.
|
|
6181
|
+
*
|
|
6182
|
+
* Its own route, distinct from the account rescan, because that is the
|
|
6183
|
+
* distinction users were missing: a model list that had fallen back to the
|
|
6184
|
+
* static table could not be recovered from the card AT ALL — "detect
|
|
6185
|
+
* accounts again" only re-read the desktop snapshots, so the only way back
|
|
6186
|
+
* to the full roster was restarting DSH.
|
|
6187
|
+
*
|
|
6188
|
+
* POST only, loopback origin only. Returns the resulting roster size per
|
|
6189
|
+
* region so the card can confirm the refresh actually changed something.
|
|
6190
|
+
*/
|
|
6191
|
+
const disposeCatalogRefresh = ctx.webServer.register({
|
|
6192
|
+
kind: "exact",
|
|
6193
|
+
path: POOL_CATALOG_REFRESH_PATH,
|
|
6194
|
+
handler: async (req, res) => {
|
|
6195
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6196
|
+
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
6197
|
+
if (deps.refreshCatalog === void 0) return json(res, 503, { error: "catalog refresh is not available in this build" });
|
|
6198
|
+
try {
|
|
6199
|
+
json(res, 200, {
|
|
6200
|
+
ok: true,
|
|
6201
|
+
...await deps.refreshCatalog()
|
|
6202
|
+
});
|
|
6203
|
+
} catch (error) {
|
|
6204
|
+
json(res, 500, { error: safeMessage(error) });
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
});
|
|
5975
6208
|
const disposeRescan = ctx.webServer.register({
|
|
5976
6209
|
kind: "exact",
|
|
5977
6210
|
path: POOL_RESCAN_PATH,
|
|
@@ -6169,6 +6402,7 @@ function registerPoolStatusRoute(ctx, deps) {
|
|
|
6169
6402
|
disposeAutomationRun();
|
|
6170
6403
|
disposeCreditReserve();
|
|
6171
6404
|
disposeAccountIgnore();
|
|
6405
|
+
disposeCatalogRefresh();
|
|
6172
6406
|
disposeCheckin();
|
|
6173
6407
|
disposeAccountDisable();
|
|
6174
6408
|
disposeModelsSave();
|
|
@@ -6537,6 +6771,51 @@ function apply(ctx, config = {}) {
|
|
|
6537
6771
|
core.pool.applyIgnored(ignoredAccounts.map((entry) => entry.id));
|
|
6538
6772
|
};
|
|
6539
6773
|
/**
|
|
6774
|
+
* Fetch and install the model catalog for both regions.
|
|
6775
|
+
*
|
|
6776
|
+
* Each region is isolated AND independently retried: they are separate
|
|
6777
|
+
* gateways on separate hosts, so one being unreachable must not degrade the
|
|
6778
|
+
* other. The failure this exists for is a startup network hiccup — several
|
|
6779
|
+
* components reporting `fetch failed` inside the same second, with the next
|
|
6780
|
+
* request succeeding 0.7s later — which used to cost the user their model
|
|
6781
|
+
* list for the whole session, with no way back short of a DSH restart.
|
|
6782
|
+
*
|
|
6783
|
+
* Returns what each region ended up with, so the card can confirm a manual
|
|
6784
|
+
* refresh actually changed something.
|
|
6785
|
+
*/
|
|
6786
|
+
const seedCatalog = async () => {
|
|
6787
|
+
const accounts = await core.pool.scan();
|
|
6788
|
+
const regions = {};
|
|
6789
|
+
for (const region of ["cn", "global"]) try {
|
|
6790
|
+
const credential = accounts.find((account) => regionOf(account.credential.domain) === region)?.credential;
|
|
6791
|
+
if (credential === void 0) {
|
|
6792
|
+
ctx.logger.info?.(`dsh-workbuddy-xdpool: no ${region} account yet; keeping the static ${region} catalog`);
|
|
6793
|
+
regions[region] = {
|
|
6794
|
+
source: "fallback",
|
|
6795
|
+
models: core.catalogs[region].current().length
|
|
6796
|
+
};
|
|
6797
|
+
continue;
|
|
6798
|
+
}
|
|
6799
|
+
const models = await core.client.fetchModels(credential);
|
|
6800
|
+
core.catalogs[region].updateFromUpstream(models);
|
|
6801
|
+
ctx.logger.info?.(`dsh-workbuddy-xdpool: ${region} catalog seeded with ${models.length} model(s)`);
|
|
6802
|
+
regions[region] = {
|
|
6803
|
+
source: "live",
|
|
6804
|
+
models: models.length
|
|
6805
|
+
};
|
|
6806
|
+
} catch (error) {
|
|
6807
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6808
|
+
core.catalogs[region].noteFetchFailure(message);
|
|
6809
|
+
ctx.logger.warn(`dsh-workbuddy-xdpool: ${region} model catalog unavailable; using static fallback`, error);
|
|
6810
|
+
regions[region] = {
|
|
6811
|
+
source: core.catalogs[region].currentSource(),
|
|
6812
|
+
models: core.catalogs[region].current().length,
|
|
6813
|
+
error: message
|
|
6814
|
+
};
|
|
6815
|
+
}
|
|
6816
|
+
return { regions };
|
|
6817
|
+
};
|
|
6818
|
+
/**
|
|
6540
6819
|
* Invalidate the provider snapshot so the picker re-reads the catalog.
|
|
6541
6820
|
*
|
|
6542
6821
|
* Seeded with a no-op and reassigned once the adapters exist. The settings
|
|
@@ -6741,7 +7020,12 @@ function apply(ctx, config = {}) {
|
|
|
6741
7020
|
refreshIgnored();
|
|
6742
7021
|
if (!ignored) await core.pool.scan();
|
|
6743
7022
|
},
|
|
6744
|
-
ignoredAccounts: () => ignoredAccounts
|
|
7023
|
+
ignoredAccounts: () => ignoredAccounts,
|
|
7024
|
+
refreshCatalog: async () => {
|
|
7025
|
+
const result = await seedCatalog();
|
|
7026
|
+
invalidateCatalog();
|
|
7027
|
+
return result;
|
|
7028
|
+
}
|
|
6745
7029
|
}));
|
|
6746
7030
|
api = {
|
|
6747
7031
|
...core,
|
|
@@ -6752,6 +7036,8 @@ function apply(ctx, config = {}) {
|
|
|
6752
7036
|
async rescan() {
|
|
6753
7037
|
const accounts = await core.pool.scan();
|
|
6754
7038
|
ctx.logger.info?.(`dsh-workbuddy-xdpool: discovered ${accounts.length} account(s)`);
|
|
7039
|
+
await seedCatalog();
|
|
7040
|
+
invalidateCatalog();
|
|
6755
7041
|
return accounts.length;
|
|
6756
7042
|
},
|
|
6757
7043
|
async status(includeCredits = false) {
|
|
@@ -6852,19 +7138,7 @@ function apply(ctx, config = {}) {
|
|
|
6852
7138
|
ctx.logger.warn("dsh-workbuddy-xdpool: account discovery failed", error);
|
|
6853
7139
|
});
|
|
6854
7140
|
(async () => {
|
|
6855
|
-
|
|
6856
|
-
for (const region of ["cn", "global"]) try {
|
|
6857
|
-
const credential = accounts.find((account) => regionOf(account.credential.domain) === region)?.credential;
|
|
6858
|
-
if (credential === void 0) {
|
|
6859
|
-
ctx.logger.info?.(`dsh-workbuddy-xdpool: no ${region} account yet; keeping the static ${region} catalog`);
|
|
6860
|
-
continue;
|
|
6861
|
-
}
|
|
6862
|
-
const models = await core.client.fetchModels(credential);
|
|
6863
|
-
core.catalogs[region].updateFromUpstream(models);
|
|
6864
|
-
ctx.logger.info?.(`dsh-workbuddy-xdpool: ${region} catalog seeded with ${models.length} model(s)`);
|
|
6865
|
-
} catch (error) {
|
|
6866
|
-
ctx.logger.warn(`dsh-workbuddy-xdpool: ${region} model catalog unavailable; using static fallback`, error);
|
|
6867
|
-
}
|
|
7141
|
+
await seedCatalog();
|
|
6868
7142
|
invalidateCatalog();
|
|
6869
7143
|
})().catch((error) => {
|
|
6870
7144
|
ctx.logger.warn("dsh-workbuddy-xdpool: account catalog seed failed", error);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-workbuddy-xdpool",
|
|
3
3
|
"displayName": "DSH WorkBuddy XD Pool",
|
|
4
4
|
"description": "Merge every locally signed-in WorkBuddy account into DeepSeek Harness as one auto-failing-over model pool (multi-account rotation, live credits, daily check-in and model catalog).",
|
|
5
|
-
"version": "1.7.
|
|
5
|
+
"version": "1.7.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "XDTrees",
|
|
8
8
|
"homepage": "https://github.com/XDTrees/dsh-workbuddy-xdpool#readme",
|