dsh-workbuddy-xdpool 0.4.2 → 1.0.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 +220 -153
- package/LICENSE +1 -1
- package/README.en.md +163 -163
- package/README.md +181 -181
- package/lib/bin.js +110 -10
- package/lib/client.js +109 -13
- package/lib/index.d.ts +72 -8
- package/lib/index.js +186 -27
- package/package.json +126 -109
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readFile, readdir } from "node:fs/promises";
|
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
7
7
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
8
|
-
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
8
|
+
import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
9
9
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
10
10
|
import { createServer } from "node:http";
|
|
11
11
|
import { Readable } from "node:stream";
|
|
@@ -774,6 +774,26 @@ function candidateAuthDirs(env = process.env) {
|
|
|
774
774
|
* Read-only pool of every discovered WorkBuddy account, with rate-limit
|
|
775
775
|
* cooldown and round-robin failover.
|
|
776
776
|
*/
|
|
777
|
+
/** Idle bonus per hour an account has been unused (reference-panel default). */
|
|
778
|
+
const IDLE_WEIGHT_PER_HOUR = .5;
|
|
779
|
+
/** Ceiling for the idle bonus, so an idle account cannot dominate forever. */
|
|
780
|
+
const IDLE_WEIGHT_MAX = 5;
|
|
781
|
+
/**
|
|
782
|
+
* Weight one account by how long it has been idle.
|
|
783
|
+
*
|
|
784
|
+
* The base of 1 keeps every eligible account in play: an account that served a
|
|
785
|
+
* moment ago still has a small chance, so a single unhealthy account cannot pin
|
|
786
|
+
* the pool to itself, and the weights never sum to zero.
|
|
787
|
+
*
|
|
788
|
+
* `lastUsedAt === undefined` means "never used in this process", which earns the
|
|
789
|
+
* full bonus: on a fresh start every account ties, and the weighted draw spreads
|
|
790
|
+
* the first requests instead of always picking the first entry.
|
|
791
|
+
*/
|
|
792
|
+
function idleWeight(lastUsedAt, now) {
|
|
793
|
+
if (lastUsedAt === void 0) return 6;
|
|
794
|
+
const hours = (now - lastUsedAt) / 36e5;
|
|
795
|
+
return 1 + Math.min(Math.max(hours, 0) * IDLE_WEIGHT_PER_HOUR, IDLE_WEIGHT_MAX);
|
|
796
|
+
}
|
|
777
797
|
var WorkBuddyAccountPool = class {
|
|
778
798
|
logger;
|
|
779
799
|
authDirs;
|
|
@@ -786,6 +806,24 @@ var WorkBuddyAccountPool = class {
|
|
|
786
806
|
cursor = 0;
|
|
787
807
|
lastScanAtMs = 0;
|
|
788
808
|
preferredId;
|
|
809
|
+
/**
|
|
810
|
+
* Account ids the user switched off on the card.
|
|
811
|
+
*
|
|
812
|
+
* Disabling is a user preference rather than a property of the credential:
|
|
813
|
+
* `scan()` rebuilds every account object from the auth files, so the set
|
|
814
|
+
* lives on the pool and is re-applied from settings after each scan.
|
|
815
|
+
*/
|
|
816
|
+
disabledIds = /* @__PURE__ */ new Set();
|
|
817
|
+
/**
|
|
818
|
+
* Last time each account served a request, epoch ms. Drives the idle term
|
|
819
|
+
* of the priority-mode weighting below: an account that just served loses to
|
|
820
|
+
* one that has been idle, so a small pool stops hammering a single account.
|
|
821
|
+
*
|
|
822
|
+
* In-memory on purpose: it only biases the next pick, so a cold start that
|
|
823
|
+
* treats every account as idle is the right default. Not keyed by id lookup
|
|
824
|
+
* misses because a removed account simply disappears from the map on re-scan.
|
|
825
|
+
*/
|
|
826
|
+
lastUsedAt = /* @__PURE__ */ new Map();
|
|
789
827
|
refreshInflight = /* @__PURE__ */ new Map();
|
|
790
828
|
constructor(options = {}) {
|
|
791
829
|
this.logger = options.logger;
|
|
@@ -804,6 +842,7 @@ var WorkBuddyAccountPool = class {
|
|
|
804
842
|
if (options.authDirs !== void 0 && options.authDirs.length > 0) this.authDirs = options.authDirs;
|
|
805
843
|
if (options.cooldownMs !== void 0 && options.cooldownMs >= 1e3) this.cooldownMs = options.cooldownMs;
|
|
806
844
|
if (options.distribution !== void 0) this.distribution = options.distribution;
|
|
845
|
+
if (options.disabledAccountIds !== void 0) this.disabledIds = new Set(options.disabledAccountIds);
|
|
807
846
|
}
|
|
808
847
|
/** Rescan the auth directories and merge newly discovered accounts. */
|
|
809
848
|
async scan() {
|
|
@@ -856,12 +895,47 @@ var WorkBuddyAccountPool = class {
|
|
|
856
895
|
*/
|
|
857
896
|
available(now, modelId, region) {
|
|
858
897
|
return this.accounts.filter((account) => {
|
|
898
|
+
if (this.disabledIds.has(account.id)) return false;
|
|
859
899
|
if (account.cooldownUntilMs > now) return false;
|
|
860
900
|
if (modelId !== void 0 && (account.modelCooldowns[modelId] ?? 0) > now) return false;
|
|
861
901
|
if (region !== void 0 && regionOf(account.credential.domain) !== region) return false;
|
|
862
902
|
return true;
|
|
863
903
|
});
|
|
864
904
|
}
|
|
905
|
+
/** Round-robin: the legacy cursor walk, kept for the distribution that asks for it. */
|
|
906
|
+
pickRoundRobin(pool) {
|
|
907
|
+
const index = this.cursor % pool.length;
|
|
908
|
+
const account = pool[index];
|
|
909
|
+
if (account === void 0) return void 0;
|
|
910
|
+
this.cursor = (index + 1) % pool.length;
|
|
911
|
+
return account;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Priority mode: weighted random over the eligible accounts.
|
|
915
|
+
*
|
|
916
|
+
* The weight is an idle bonus — `1 + min(idleHours * perHour, max)` — so an
|
|
917
|
+
* account that has never served (or has been idle for a while) outranks one
|
|
918
|
+
* that just answered. Reference panel logic drops its success-rate term
|
|
919
|
+
* entirely because a lifetime error counter penalises an account forever;
|
|
920
|
+
* instantaneous health is already handled by cooldowns, which is why those
|
|
921
|
+
* accounts never reach this list.
|
|
922
|
+
*
|
|
923
|
+
* A pool with no idle history (fresh process) hashes to equal weights, which
|
|
924
|
+
* spreads the very first picks instead of always returning index 0.
|
|
925
|
+
*/
|
|
926
|
+
pickByWeight(pool) {
|
|
927
|
+
if (pool.length === 1) return pool[0];
|
|
928
|
+
const now = Date.now();
|
|
929
|
+
const weights = pool.map((account) => idleWeight(this.lastUsedAt.get(account.id), now));
|
|
930
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
931
|
+
if (!Number.isFinite(total) || total <= 0) return pool[0];
|
|
932
|
+
let roll = Math.random() * total;
|
|
933
|
+
for (let index = 0; index < pool.length; index += 1) {
|
|
934
|
+
roll -= weights[index] ?? 0;
|
|
935
|
+
if (roll < 0) return pool[index];
|
|
936
|
+
}
|
|
937
|
+
return pool[pool.length - 1];
|
|
938
|
+
}
|
|
865
939
|
/**
|
|
866
940
|
* Pick the account to serve a request.
|
|
867
941
|
*
|
|
@@ -891,16 +965,16 @@ var WorkBuddyAccountPool = class {
|
|
|
891
965
|
}
|
|
892
966
|
if (pool.length === 0) return void 0;
|
|
893
967
|
if (this.preferredId !== void 0) {
|
|
894
|
-
const
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
|
|
968
|
+
const preferred = pool.find((account) => account.id === this.preferredId);
|
|
969
|
+
if (preferred !== void 0) {
|
|
970
|
+
this.lastUsedAt.set(preferred.id, Date.now());
|
|
971
|
+
await this.ensureFresh(preferred);
|
|
972
|
+
return preferred;
|
|
898
973
|
}
|
|
899
974
|
}
|
|
900
|
-
const
|
|
901
|
-
const account = pool[index];
|
|
975
|
+
const account = this.distribution === "round-robin" ? this.pickRoundRobin(pool) : this.distribution === "balanced" ? this.pickByWeight(pool) : pool[0];
|
|
902
976
|
if (account === void 0) return void 0;
|
|
903
|
-
|
|
977
|
+
this.lastUsedAt.set(account.id, Date.now());
|
|
904
978
|
await this.ensureFresh(account);
|
|
905
979
|
return account;
|
|
906
980
|
}
|
|
@@ -912,6 +986,14 @@ var WorkBuddyAccountPool = class {
|
|
|
912
986
|
prefer(accountId) {
|
|
913
987
|
this.preferredId = accountId;
|
|
914
988
|
}
|
|
989
|
+
/** Whether the user switched this account off on the card. */
|
|
990
|
+
isDisabled(accountId) {
|
|
991
|
+
return this.disabledIds.has(accountId);
|
|
992
|
+
}
|
|
993
|
+
/** Every account id the user switched off, in discovery order. */
|
|
994
|
+
disabledIdsInOrder() {
|
|
995
|
+
return this.accounts.filter((account) => this.disabledIds.has(account.id)).map((account) => account.id);
|
|
996
|
+
}
|
|
915
997
|
/** Best-effort refresh of one account after a session-dead upstream answer. */
|
|
916
998
|
async refreshAccount(accountId) {
|
|
917
999
|
const account = this.accounts.find((item) => item.id === accountId);
|
|
@@ -1333,10 +1415,10 @@ function createWorkBuddyAdapter(options) {
|
|
|
1333
1415
|
streamIdleTimeoutMs: WORKBUDDY_STREAM_IDLE_TIMEOUT_MS,
|
|
1334
1416
|
retryPolicy: resolveRetryPolicy(void 0, "dsh-workbuddy-xdpool retryPolicy"),
|
|
1335
1417
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
1336
|
-
modelErrors: /* @__PURE__ */ new Map(),
|
|
1337
1418
|
...REQUEST_IMAGE_BUDGETS,
|
|
1338
1419
|
piProvider: provider
|
|
1339
1420
|
};
|
|
1421
|
+
Object.assign(profile, { modelErrors: /* @__PURE__ */ new Map() });
|
|
1340
1422
|
let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
|
|
1341
1423
|
return {
|
|
1342
1424
|
providerId,
|
|
@@ -1344,7 +1426,9 @@ function createWorkBuddyAdapter(options) {
|
|
|
1344
1426
|
adapter: new PiAiAdapter({
|
|
1345
1427
|
profiles: () => profiles,
|
|
1346
1428
|
auth: INERT_AUTH,
|
|
1347
|
-
resolveApiKey: async () => shim.token()
|
|
1429
|
+
resolveApiKey: async () => shim.token(),
|
|
1430
|
+
resolveAttachments: () => options.ctx.get("attachments"),
|
|
1431
|
+
resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(attachments, (hostPath) => options.ctx.get("fs")?.processPathFromHostPath(hostPath), ref)
|
|
1348
1432
|
}),
|
|
1349
1433
|
buildModels,
|
|
1350
1434
|
invalidate: () => {
|
|
@@ -1737,6 +1821,8 @@ const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset"
|
|
|
1737
1821
|
const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
|
|
1738
1822
|
/** Plugin-owned model-selection save endpoint (writes the settings section). */
|
|
1739
1823
|
const POOL_MODELS_SAVE_PATH = "/plugins/dsh-workbuddy-xdpool/models/save";
|
|
1824
|
+
/** Switch one account in or out of the pool (card toggle). */
|
|
1825
|
+
const POOL_ACCOUNT_DISABLE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/disabled";
|
|
1740
1826
|
//#endregion
|
|
1741
1827
|
//#region src/web-status.ts
|
|
1742
1828
|
/** Redact token-like content before it crosses to the browser. */
|
|
@@ -1828,7 +1914,25 @@ function parseSelection(body) {
|
|
|
1828
1914
|
}
|
|
1829
1915
|
return out;
|
|
1830
1916
|
}
|
|
1831
|
-
|
|
1917
|
+
/**
|
|
1918
|
+
* Validate the account-toggle body. Returns undefined for anything malformed so
|
|
1919
|
+
* the route answers 400 instead of writing a half-applied switch.
|
|
1920
|
+
*
|
|
1921
|
+
* The id must name a currently known account: accepting an arbitrary string
|
|
1922
|
+
* would let a stale tab (or a renamed credential) leave orphan ids in the
|
|
1923
|
+
* settings file that no card can ever switch back off.
|
|
1924
|
+
*/
|
|
1925
|
+
function parseAccountToggle(body, known) {
|
|
1926
|
+
const accountId = typeof body["accountId"] === "string" ? body["accountId"].trim() : "";
|
|
1927
|
+
const disabled = body["disabled"];
|
|
1928
|
+
if (accountId === "" || typeof disabled !== "boolean") return void 0;
|
|
1929
|
+
if (!known(accountId)) return void 0;
|
|
1930
|
+
return {
|
|
1931
|
+
accountId,
|
|
1932
|
+
disabled
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
function toWebAccount(account, disabled) {
|
|
1832
1936
|
const now = Date.now();
|
|
1833
1937
|
const cooling = account.cooldownUntilMs > now;
|
|
1834
1938
|
const modelCooldowns = Object.entries(account.modelCooldowns).filter(([, until]) => until > now).sort((a, b) => a[1] - b[1]).map(([modelId, until]) => ({
|
|
@@ -1844,6 +1948,7 @@ function toWebAccount(account) {
|
|
|
1844
1948
|
cooling,
|
|
1845
1949
|
...cooling ? { cooldownUntil: new Date(account.cooldownUntilMs).toISOString() } : {},
|
|
1846
1950
|
...modelCooldowns.length === 0 ? {} : { modelCooldowns },
|
|
1951
|
+
disabled,
|
|
1847
1952
|
rateLimitHits: account.rateLimitHits
|
|
1848
1953
|
};
|
|
1849
1954
|
}
|
|
@@ -1876,7 +1981,7 @@ async function poolWebStatus(deps, region = "cn") {
|
|
|
1876
1981
|
const rows = [];
|
|
1877
1982
|
const now = Date.now();
|
|
1878
1983
|
for (const account of accounts) {
|
|
1879
|
-
const row = toWebAccount(account);
|
|
1984
|
+
const row = toWebAccount(account, deps.pool.isDisabled(account.id));
|
|
1880
1985
|
if (!row.cooling) {
|
|
1881
1986
|
try {
|
|
1882
1987
|
const credits = await deps.client.fetchCredits(account.credential);
|
|
@@ -2023,11 +2128,14 @@ function registerPoolStatusRoute(ctx, deps) {
|
|
|
2023
2128
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
2024
2129
|
if (deps.saveSelection === void 0) return json(res, 503, { error: "settings service unavailable; model selection cannot be saved" });
|
|
2025
2130
|
try {
|
|
2026
|
-
const
|
|
2131
|
+
const body = await readJsonBody(req);
|
|
2132
|
+
const selection = parseSelection(body);
|
|
2027
2133
|
if (selection === void 0) return json(res, 400, { error: "invalid selection payload" });
|
|
2028
|
-
|
|
2134
|
+
const region = body["region"] === "global" ? "global" : "cn";
|
|
2135
|
+
await deps.saveSelection(region, selection);
|
|
2029
2136
|
json(res, 200, {
|
|
2030
2137
|
ok: true,
|
|
2138
|
+
region,
|
|
2031
2139
|
selection
|
|
2032
2140
|
});
|
|
2033
2141
|
} catch (error) {
|
|
@@ -2035,8 +2143,31 @@ function registerPoolStatusRoute(ctx, deps) {
|
|
|
2035
2143
|
}
|
|
2036
2144
|
}
|
|
2037
2145
|
});
|
|
2146
|
+
const disposeAccountDisable = ctx.webServer.register({
|
|
2147
|
+
kind: "exact",
|
|
2148
|
+
path: POOL_ACCOUNT_DISABLE_PATH,
|
|
2149
|
+
handler: async (req, res) => {
|
|
2150
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
2151
|
+
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
2152
|
+
if (deps.setAccountDisabled === void 0) return json(res, 503, { error: "settings service unavailable; the account switch cannot be saved" });
|
|
2153
|
+
try {
|
|
2154
|
+
const body = await readJsonBody(req);
|
|
2155
|
+
const known = new Set(deps.pool.list().map((account) => account.id));
|
|
2156
|
+
const toggle = parseAccountToggle(body, (id) => known.has(id));
|
|
2157
|
+
if (toggle === void 0) return json(res, 400, { error: "invalid account toggle payload" });
|
|
2158
|
+
await deps.setAccountDisabled(toggle.accountId, toggle.disabled);
|
|
2159
|
+
json(res, 200, {
|
|
2160
|
+
ok: true,
|
|
2161
|
+
...toggle
|
|
2162
|
+
});
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
json(res, 500, { error: safeMessage(error) });
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2038
2168
|
return () => {
|
|
2039
2169
|
disposeCheckin();
|
|
2170
|
+
disposeAccountDisable();
|
|
2040
2171
|
disposeModelsSave();
|
|
2041
2172
|
disposeReset();
|
|
2042
2173
|
disposeRescan();
|
|
@@ -2067,6 +2198,21 @@ const WORKBUDDY_POOL_SETTINGS_NS = "workbuddy-xdpool";
|
|
|
2067
2198
|
/** Upper bound the card offers as the "default" context window, in tokens. */
|
|
2068
2199
|
const DEFAULT_CONTEXT_BUDGET = 2e5;
|
|
2069
2200
|
/**
|
|
2201
|
+
* One region's model-selection schema.
|
|
2202
|
+
*
|
|
2203
|
+
* Every field is optional on purpose: an absent field keeps its documented
|
|
2204
|
+
* meaning ("all enabled" / "follow the upstream image flag" / "no cap"), and a
|
|
2205
|
+
* region that has never been saved stays absent so `applyConfigFromSource` can
|
|
2206
|
+
* fall back to the legacy flat keys.
|
|
2207
|
+
*/
|
|
2208
|
+
const modelSelectionSchema = z.object({
|
|
2209
|
+
enabledModelIds: z.array(z.string()).description("Model ids enabled in this region's picker (absent = all)"),
|
|
2210
|
+
imageModelIds: z.array(z.string()).description("Model ids accepting image input in this region (absent = follow upstream)"),
|
|
2211
|
+
contextBudgets: z.dict(z.number().step(1).min(1)).description("Per-model context-window override for this region")
|
|
2212
|
+
});
|
|
2213
|
+
/** Settings key holding one region's saved selection. */
|
|
2214
|
+
const modelSelectionKeyFor = (region) => region === "cn" ? "modelSelectionCn" : "modelSelectionGlobal";
|
|
2215
|
+
/**
|
|
2070
2216
|
* Plugin configuration schema.
|
|
2071
2217
|
*
|
|
2072
2218
|
* Mirrors the shape the settings section stores. Every field carries a default
|
|
@@ -2080,9 +2226,12 @@ const Config = z.object({
|
|
|
2080
2226
|
authFile: z.string().description("WorkBuddy desktop auth file (defaults to the app own location)"),
|
|
2081
2227
|
cooldownMs: z.number().step(1).min(1e3).default(6e4).description("Rate-limit cooldown per account, in milliseconds"),
|
|
2082
2228
|
distribution: z.union(["priority", "round-robin"]).default("priority").description("How requests are spread: priority (drain one) or round-robin"),
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2229
|
+
disabledAccountIds: z.array(z.string()).default([]).description("Account ids excluded from the pool (empty = every discovered account participates)"),
|
|
2230
|
+
enabledModelIds: z.array(z.string()).default([]).description("Legacy shared model-id list; used by a region that has no per-region selection yet"),
|
|
2231
|
+
imageModelIds: z.array(z.string()).default([]).description("Legacy shared image-id list; used by a region that has no per-region selection yet"),
|
|
2232
|
+
contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Legacy shared context budgets; used by a region with no per-region selection yet"),
|
|
2233
|
+
modelSelectionCn: modelSelectionSchema.description("Model selection for the domestic gateway"),
|
|
2234
|
+
modelSelectionGlobal: modelSelectionSchema.description("Model selection for the international gateway")
|
|
2086
2235
|
});
|
|
2087
2236
|
/** Live API, published for the CLI. */
|
|
2088
2237
|
let api;
|
|
@@ -2147,19 +2296,20 @@ function apply(ctx, config = {}) {
|
|
|
2147
2296
|
}
|
|
2148
2297
|
};
|
|
2149
2298
|
const applyConfigFromSource = () => {
|
|
2150
|
-
const { authFile, cooldownMs, distribution, enabledModelIds, imageModelIds, contextBudgets } = current();
|
|
2299
|
+
const { authFile, cooldownMs, distribution, disabledAccountIds, enabledModelIds, imageModelIds, contextBudgets, modelSelectionCn, modelSelectionGlobal } = current();
|
|
2151
2300
|
core.pool.applyConfig({
|
|
2152
2301
|
...authFile === void 0 ? {} : { authDirs: [dirname(authFile)] },
|
|
2153
2302
|
...cooldownMs === void 0 ? {} : { cooldownMs },
|
|
2154
|
-
distribution: distribution ?? "priority"
|
|
2303
|
+
distribution: distribution ?? "priority",
|
|
2304
|
+
...disabledAccountIds === void 0 ? {} : { disabledAccountIds }
|
|
2155
2305
|
});
|
|
2156
|
-
const
|
|
2306
|
+
const legacySelection = {
|
|
2157
2307
|
...enabledModelIds === void 0 ? {} : { enabledModelIds },
|
|
2158
2308
|
...imageModelIds === void 0 ? {} : { imageModelIds },
|
|
2159
2309
|
...contextBudgets === void 0 ? {} : { contextBudgets }
|
|
2160
2310
|
};
|
|
2161
|
-
core.catalogs.cn.applySelection(
|
|
2162
|
-
core.catalogs.global.applySelection(
|
|
2311
|
+
core.catalogs.cn.applySelection(modelSelectionCn ?? legacySelection);
|
|
2312
|
+
core.catalogs.global.applySelection(modelSelectionGlobal ?? legacySelection);
|
|
2163
2313
|
};
|
|
2164
2314
|
const settingsService = ctx.settings;
|
|
2165
2315
|
if (typeof settingsService.installSection === "function") settingsService.installSection(ctx, WORKBUDDY_POOL_SETTINGS_NS, Config, config, sectionHooks);
|
|
@@ -2218,10 +2368,17 @@ function apply(ctx, config = {}) {
|
|
|
2218
2368
|
catalogs: core.catalogs,
|
|
2219
2369
|
client: core.client,
|
|
2220
2370
|
shim: () => shimInfo("cn"),
|
|
2221
|
-
saveSelection: (selection) => {
|
|
2222
|
-
setSetting(
|
|
2223
|
-
|
|
2224
|
-
|
|
2371
|
+
saveSelection: (region, selection) => {
|
|
2372
|
+
setSetting(modelSelectionKeyFor(region), {
|
|
2373
|
+
...selection.enabledModelIds === void 0 ? {} : { enabledModelIds: [...selection.enabledModelIds] },
|
|
2374
|
+
...selection.imageModelIds === void 0 ? {} : { imageModelIds: [...selection.imageModelIds] },
|
|
2375
|
+
...selection.contextBudgets === void 0 ? {} : { contextBudgets: { ...selection.contextBudgets } }
|
|
2376
|
+
});
|
|
2377
|
+
},
|
|
2378
|
+
setAccountDisabled: (accountId, disabled) => {
|
|
2379
|
+
const currentIds = current().disabledAccountIds ?? [];
|
|
2380
|
+
const next = disabled ? currentIds.includes(accountId) ? currentIds : [...currentIds, accountId] : currentIds.filter((id) => id !== accountId);
|
|
2381
|
+
setSetting("disabledAccountIds", next);
|
|
2225
2382
|
}
|
|
2226
2383
|
}));
|
|
2227
2384
|
api = {
|
|
@@ -2253,12 +2410,14 @@ function apply(ctx, config = {}) {
|
|
|
2253
2410
|
try {
|
|
2254
2411
|
const adaptersByRegion = {
|
|
2255
2412
|
cn: createWorkBuddyAdapter({
|
|
2413
|
+
ctx,
|
|
2256
2414
|
shim: shims.cn,
|
|
2257
2415
|
catalog: core.catalogs.cn,
|
|
2258
2416
|
providerId: POOL_PROVIDER_BY_REGION.cn,
|
|
2259
2417
|
displayName: POOL_NAME_BY_REGION.cn
|
|
2260
2418
|
}),
|
|
2261
2419
|
global: createWorkBuddyAdapter({
|
|
2420
|
+
ctx,
|
|
2262
2421
|
shim: shims.global,
|
|
2263
2422
|
catalog: core.catalogs.global,
|
|
2264
2423
|
providerId: POOL_PROVIDER_BY_REGION.global,
|
|
@@ -2350,4 +2509,4 @@ function apply(ctx, config = {}) {
|
|
|
2350
2509
|
});
|
|
2351
2510
|
}
|
|
2352
2511
|
//#endregion
|
|
2353
|
-
export { Config, DEFAULT_CONTEXT_BUDGET, FALLBACK_WORKBUDDY_MODELS, POOL_CHECKIN_PATH, POOL_MODELS_SAVE_PATH, POOL_RESCAN_PATH, POOL_RESET_COOLDOWN_PATH, POOL_STATUS_PATH, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_LIVE_FILENAME, WORKBUDDY_POOL_PROVIDER, WORKBUDDY_POOL_SETTINGS_NS, WorkBuddyAccountPool, WorkBuddyCatalog, WorkBuddyUpstreamClient, apply, buildStatus, candidateAuthDirs, classifyUpstreamError, createCore, createWorkBuddyAdapter, createWorkBuddyShim, currentApi, defaultDesktopAuthDirs, formatRates, formatStatus, inject, name, parseRateLimitReset, parseWorkBuddyAuth, setApi, workbuddyAccountId };
|
|
2512
|
+
export { Config, DEFAULT_CONTEXT_BUDGET, FALLBACK_WORKBUDDY_MODELS, POOL_CHECKIN_PATH, POOL_MODELS_SAVE_PATH, POOL_RESCAN_PATH, POOL_RESET_COOLDOWN_PATH, POOL_STATUS_PATH, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_LIVE_FILENAME, WORKBUDDY_POOL_PROVIDER, WORKBUDDY_POOL_SETTINGS_NS, WorkBuddyAccountPool, WorkBuddyCatalog, WorkBuddyUpstreamClient, apply, buildStatus, candidateAuthDirs, classifyUpstreamError, createCore, createWorkBuddyAdapter, createWorkBuddyShim, currentApi, defaultDesktopAuthDirs, formatRates, formatStatus, inject, modelSelectionKeyFor, name, parseRateLimitReset, parseWorkBuddyAuth, setApi, workbuddyAccountId };
|
package/package.json
CHANGED
|
@@ -1,109 +1,126 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-workbuddy-xdpool",
|
|
3
|
-
"displayName": "DSH WorkBuddy XD Pool",
|
|
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": "0.
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"author": "
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"./
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"@deepseek-ai/dsh-client-
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-settings
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-
|
|
67
|
-
"@deepseek-ai/dsh-client-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"@deepseek-ai/
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/
|
|
78
|
-
"@
|
|
79
|
-
"@
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"@deepseek-ai/dsh-
|
|
84
|
-
"@deepseek-ai/dsh-
|
|
85
|
-
"@deepseek-ai/dsh-
|
|
86
|
-
"@deepseek-ai/dsh-
|
|
87
|
-
"@deepseek-ai/
|
|
88
|
-
"@
|
|
89
|
-
"
|
|
90
|
-
"@types/react": "~18.3.31",
|
|
91
|
-
"@types/react-dom": "~18.3.0"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"@deepseek-ai/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"@deepseek-ai/dsh-
|
|
103
|
-
"@deepseek-ai/
|
|
104
|
-
"@
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"@deepseek-ai/dsh-
|
|
108
|
-
|
|
109
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-workbuddy-xdpool",
|
|
3
|
+
"displayName": "DSH WorkBuddy XD Pool",
|
|
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.0.0",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "XDTrees",
|
|
8
|
+
"homepage": "https://github.com/XDTrees/dsh-workbuddy-xdpool#readme",
|
|
9
|
+
"bugs": { "url": "https://github.com/XDTrees/dsh-workbuddy-xdpool/issues" },
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/XDTrees/dsh-workbuddy-xdpool.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dsh-plugin",
|
|
16
|
+
"deepseek-harness",
|
|
17
|
+
"workbuddy",
|
|
18
|
+
"codebuddy",
|
|
19
|
+
"model-provider",
|
|
20
|
+
"multi-account",
|
|
21
|
+
"failover"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
26
|
+
},
|
|
27
|
+
"main": "lib/index.js",
|
|
28
|
+
"types": "lib/index.d.ts",
|
|
29
|
+
"bin": {
|
|
30
|
+
"dsh-workbuddy-xdpool": "lib/bin.js"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./lib/index.d.ts",
|
|
35
|
+
"default": "./lib/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./client": "./lib/client.js",
|
|
38
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"lib",
|
|
43
|
+
"cordis.patch.yml",
|
|
44
|
+
"screenshots.json",
|
|
45
|
+
"assets",
|
|
46
|
+
"README.md",
|
|
47
|
+
"README.en.md",
|
|
48
|
+
"CHANGELOG.md",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsdown",
|
|
53
|
+
"prepublishOnly": "tsdown",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
56
|
+
"typecheck:client": "tsc -p tsconfig.client.json --noEmit"
|
|
57
|
+
},
|
|
58
|
+
"dsh": {
|
|
59
|
+
"bundle": {
|
|
60
|
+
"patch": "./cordis.patch.yml"
|
|
61
|
+
},
|
|
62
|
+
"client": {
|
|
63
|
+
"inject": [
|
|
64
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-primitives",
|
|
69
|
+
"@deepseek-ai/dsh-client-locale"
|
|
70
|
+
],
|
|
71
|
+
"platform": "web"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
|
|
83
|
+
"@deepseek-ai/dsh-launch-environment": "0.1.2-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.1.2-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
|
87
|
+
"@deepseek-ai/schemastery": "3.18.2",
|
|
88
|
+
"@earendil-works/pi-ai": "0.84.3",
|
|
89
|
+
"@types/node": "^26.4.1",
|
|
90
|
+
"@types/react": "~18.3.31",
|
|
91
|
+
"@types/react-dom": "~18.3.0",
|
|
92
|
+
"react": "^18.2.0",
|
|
93
|
+
"tsdown": "^0.23.0",
|
|
94
|
+
"typescript": "^7.0.2",
|
|
95
|
+
"vite": "8.3.0",
|
|
96
|
+
"vitest": "^5.0.0"
|
|
97
|
+
},
|
|
98
|
+
"peerDependencies": {
|
|
99
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-llm": ">=0.1.1-rc.1 <0.2.0",
|
|
101
|
+
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.1-rc.1 <0.2.0",
|
|
102
|
+
"@deepseek-ai/dsh-settings": ">=0.1.1-rc.1 <0.2.0",
|
|
103
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
104
|
+
"@earendil-works/pi-ai": ">=0.82.1 <0.85.0"
|
|
105
|
+
},
|
|
106
|
+
"peerDependenciesMeta": {
|
|
107
|
+
"@deepseek-ai/dsh-llm": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"@deepseek-ai/dsh-llm-pi-ai": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
113
|
+
"@deepseek-ai/schemastery": {
|
|
114
|
+
"optional": true
|
|
115
|
+
},
|
|
116
|
+
"@earendil-works/pi-ai": {
|
|
117
|
+
"optional": true
|
|
118
|
+
},
|
|
119
|
+
"@deepseek-ai/cordis": {
|
|
120
|
+
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"@deepseek-ai/dsh-settings": {
|
|
123
|
+
"optional": true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|