dsh-connect-workbuddy 1.4.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.en.md +14 -10
- package/README.md +14 -10
- package/lib/bin.d.ts +3 -1
- package/lib/bin.js +103 -69
- package/lib/client.js +185 -64
- package/lib/{host-heartbeat-DQQwiUtm.js → host-heartbeat-DjNRPvQf.js} +1245 -1141
- package/lib/index.d.ts +145 -29
- package/lib/index.js +197 -97
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as prepareChatBody, C as workbuddyAccountId, D as parseCreditMultiplier, E as classifyUpstreamError, O as parseReasoning, S as parseWorkBuddyAuth, T as WorkBuddyUpstreamClient, _ as authFileName, a as readHostHeartbeat, b as defaultDesktopAuthPath, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, i as processStartTimeMs, j as regionOf, k as parseUpstreamModel, l as FALLBACK_WORKBUDDY_MODELS, m as WORKBUDDY_AUTH_FILENAME, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyOwnAuthPath, x as legacyWorkbuddyOwnAuthPath, y as defaultDesktopAuthDirs } from "./host-heartbeat-DjNRPvQf.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
@@ -9,9 +9,11 @@ import { createServer } from "node:http";
|
|
|
9
9
|
import { Readable } from "node:stream";
|
|
10
10
|
//#region src/adapter.ts
|
|
11
11
|
/**
|
|
12
|
-
* The
|
|
12
|
+
* The WorkBuddy pi-ai providers: loopback-backed adapters registered
|
|
13
13
|
* into the Harness LLM seam, assembled from public `dsh-llm-pi-ai`
|
|
14
|
-
* extension points.
|
|
14
|
+
* extension points. One instance per region — `workbuddy` for the domestic
|
|
15
|
+
* gateway, `workbuddy-global` for the international one — each pointing at
|
|
16
|
+
* its own shim and catalog so the two regions serve simultaneously.
|
|
15
17
|
*
|
|
16
18
|
* 参考:corrinehu/dsh-workbuddy-connect(MIT,Copyright (c) 2026 Corrine Hu)
|
|
17
19
|
* — pi-ai provider 的装配方式(createProvider + openAICompletionsApi +
|
|
@@ -19,12 +21,28 @@ import { Readable } from "node:stream";
|
|
|
19
21
|
* DSH 插件结构与 provider 注册的思路参照
|
|
20
22
|
* franksong2702/dsh-codex-connect(Apache-2.0),经其转引。
|
|
21
23
|
* 改动:模型描述符补上 upstream 给出的多模态与推理档位信息(若有),
|
|
22
|
-
* 供 DSH
|
|
24
|
+
* 供 DSH 的能力判断使用;工厂参数化 provider id,支持双区域实例。
|
|
23
25
|
*
|
|
24
26
|
* @module dsh-connect-workbuddy/adapter
|
|
25
27
|
*/
|
|
26
|
-
/** Provider route this bundle owns. */
|
|
28
|
+
/** Provider route this bundle owns for the domestic (CN) gateway. */
|
|
27
29
|
const WORKBUDDY_PROVIDER = "workbuddy";
|
|
30
|
+
/** Provider route this bundle owns for the international gateway. */
|
|
31
|
+
const WORKBUDDY_GLOBAL_PROVIDER = "workbuddy-global";
|
|
32
|
+
/** The provider id each region registers as. */
|
|
33
|
+
const WORKBUDDY_PROVIDERS = {
|
|
34
|
+
cn: WORKBUDDY_PROVIDER,
|
|
35
|
+
global: WORKBUDDY_GLOBAL_PROVIDER
|
|
36
|
+
};
|
|
37
|
+
/** Region a provider route id belongs to. */
|
|
38
|
+
function regionOfProvider(provider) {
|
|
39
|
+
for (const [region, id] of Object.entries(WORKBUDDY_PROVIDERS)) if (id === provider) return region;
|
|
40
|
+
}
|
|
41
|
+
/** Human-readable provider name, shown in the DSH model picker. */
|
|
42
|
+
const WORKBUDDY_PROVIDER_DISPLAY_NAMES = {
|
|
43
|
+
cn: "WorkBuddy",
|
|
44
|
+
global: "WorkBuddy Global"
|
|
45
|
+
};
|
|
28
46
|
/** Provider idle ceiling while one stream read is outstanding. */
|
|
29
47
|
const WORKBUDDY_STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
30
48
|
/**
|
|
@@ -103,13 +121,13 @@ function workBuddyThinkingLevelMap(info) {
|
|
|
103
121
|
return map;
|
|
104
122
|
}
|
|
105
123
|
/** Build one pi-ai model descriptor pointing at the loopback shim. */
|
|
106
|
-
function toPiModel(info, baseUrl) {
|
|
124
|
+
function toPiModel(info, baseUrl, providerId) {
|
|
107
125
|
const thinkingLevelMap = workBuddyThinkingLevelMap(info);
|
|
108
126
|
return {
|
|
109
127
|
id: info.id,
|
|
110
128
|
name: workBuddyDisplayName(info),
|
|
111
129
|
api: "openai-completions",
|
|
112
|
-
provider:
|
|
130
|
+
provider: providerId,
|
|
113
131
|
baseUrl,
|
|
114
132
|
input: workBuddyModelInput(info),
|
|
115
133
|
cost: NO_COST,
|
|
@@ -127,14 +145,16 @@ function toPiModel(info, baseUrl) {
|
|
|
127
145
|
*/
|
|
128
146
|
function createWorkBuddyAdapter(options) {
|
|
129
147
|
const { shim, store, catalog, resolveAttachments } = options;
|
|
148
|
+
const providerId = options.provider ?? "workbuddy";
|
|
149
|
+
const providerName = options.displayName ?? "WorkBuddy";
|
|
130
150
|
const buildModels = () => {
|
|
131
151
|
const baseUrl = `${shim.baseUrl()}/v1`;
|
|
132
|
-
return catalog.current().map((info) => toPiModel(info, baseUrl));
|
|
152
|
+
return catalog.current().map((info) => toPiModel(info, baseUrl, providerId));
|
|
133
153
|
};
|
|
134
154
|
const provider = {
|
|
135
155
|
...createProvider({
|
|
136
|
-
id:
|
|
137
|
-
name:
|
|
156
|
+
id: providerId,
|
|
157
|
+
name: providerName,
|
|
138
158
|
auth: { apiKey: {
|
|
139
159
|
name: "WorkBuddy OAuth bearer token",
|
|
140
160
|
async resolve({ credential }) {
|
|
@@ -151,8 +171,8 @@ function createWorkBuddyAdapter(options) {
|
|
|
151
171
|
getModels: () => buildModels()
|
|
152
172
|
};
|
|
153
173
|
const profile = {
|
|
154
|
-
provider:
|
|
155
|
-
displayName:
|
|
174
|
+
provider: providerId,
|
|
175
|
+
displayName: providerName,
|
|
156
176
|
streamIdleTimeoutMs: WORKBUDDY_STREAM_IDLE_TIMEOUT_MS,
|
|
157
177
|
retryPolicy: resolveRetryPolicy(void 0, "dsh-connect-workbuddy retryPolicy"),
|
|
158
178
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
@@ -160,7 +180,7 @@ function createWorkBuddyAdapter(options) {
|
|
|
160
180
|
...REQUEST_IMAGE_BUDGETS,
|
|
161
181
|
piProvider: provider
|
|
162
182
|
};
|
|
163
|
-
let profiles = /* @__PURE__ */ new Map([[
|
|
183
|
+
let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
|
|
164
184
|
return {
|
|
165
185
|
adapter: new PiAiAdapter({
|
|
166
186
|
profiles: () => profiles,
|
|
@@ -169,7 +189,7 @@ function createWorkBuddyAdapter(options) {
|
|
|
169
189
|
...resolveAttachments === void 0 ? {} : { resolveAttachments }
|
|
170
190
|
}),
|
|
171
191
|
invalidate: () => {
|
|
172
|
-
profiles = /* @__PURE__ */ new Map([[
|
|
192
|
+
profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
|
|
173
193
|
}
|
|
174
194
|
};
|
|
175
195
|
}
|
|
@@ -424,6 +444,28 @@ const WORKBUDDY_MODELS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/models/ref
|
|
|
424
444
|
const WORKBUDDY_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-workbuddy/accounts/refresh";
|
|
425
445
|
/** Plugin-owned daily check-in action endpoint. */
|
|
426
446
|
const WORKBUDDY_CHECKIN_PATH = "/plugins/dsh-connect-workbuddy/checkin";
|
|
447
|
+
/** Query parameter naming the region a card request addresses. */
|
|
448
|
+
const WORKBUDDY_REGION_PARAM = "region";
|
|
449
|
+
/** Every region, in card tab order. */
|
|
450
|
+
const WORKBUDDY_REGIONS = ["cn", "global"];
|
|
451
|
+
/**
|
|
452
|
+
* Address one region's status route. The two regions are separate provider
|
|
453
|
+
* stacks; every card request carries the region whose tab the user is on.
|
|
454
|
+
*/
|
|
455
|
+
function withWorkBuddyRegion(path, region) {
|
|
456
|
+
return `${path}?${WORKBUDDY_REGION_PARAM}=${region}`;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Read the region parameter off a status-route URL. Absent means the domestic
|
|
460
|
+
* tab (`cn`); a present-but-unknown value returns undefined so the route can
|
|
461
|
+
* answer 400 instead of guessing.
|
|
462
|
+
*/
|
|
463
|
+
function regionOfStatusUrl(url) {
|
|
464
|
+
const at = url.indexOf("?");
|
|
465
|
+
const value = at === -1 ? null : new URLSearchParams(url.slice(at + 1)).get(WORKBUDDY_REGION_PARAM);
|
|
466
|
+
if (value === null || value === "") return "cn";
|
|
467
|
+
return WORKBUDDY_REGIONS.includes(value) ? value : void 0;
|
|
468
|
+
}
|
|
427
469
|
/**
|
|
428
470
|
* Project one card row into its persisted `lastCatalog` shape: the native
|
|
429
471
|
* context window becomes the stored `contextWindow`, and the card-only
|
|
@@ -510,19 +552,22 @@ function toWebAccount(account) {
|
|
|
510
552
|
};
|
|
511
553
|
}
|
|
512
554
|
/**
|
|
513
|
-
* Assemble
|
|
514
|
-
*
|
|
515
|
-
*
|
|
555
|
+
* Assemble one region's card document. `region` is the tab the card is on;
|
|
556
|
+
* the region-scoped store already answers with only that region's accounts,
|
|
557
|
+
* so the document's model slots and account list are that region's by
|
|
558
|
+
* construction. Sign-in state is read-only; credit is a live billing answer
|
|
559
|
+
* whose failure degrades to `creditsError` rather than failing the document.
|
|
516
560
|
*/
|
|
517
|
-
async function workBuddyWebStatus(deps) {
|
|
518
|
-
const
|
|
519
|
-
|
|
561
|
+
async function workBuddyWebStatus(deps, region) {
|
|
562
|
+
const store = deps.store(region);
|
|
563
|
+
const accounts = await store.accounts();
|
|
564
|
+
if ((await store.status()).state !== "signed-out" && accounts.length === 0) return {
|
|
520
565
|
status: "signed-out",
|
|
521
566
|
accounts: []
|
|
522
567
|
};
|
|
523
568
|
let credential;
|
|
524
569
|
try {
|
|
525
|
-
credential = await
|
|
570
|
+
credential = await store.resolve();
|
|
526
571
|
} catch (error) {
|
|
527
572
|
return {
|
|
528
573
|
status: "signed-out",
|
|
@@ -530,10 +575,8 @@ async function workBuddyWebStatus(deps) {
|
|
|
530
575
|
message: safeMessage(error)
|
|
531
576
|
};
|
|
532
577
|
}
|
|
533
|
-
const selected = accounts.find((account) => account.selected);
|
|
534
|
-
const region = regionOf(credential.domain);
|
|
535
578
|
const account = {
|
|
536
|
-
accountId: selected?.id ?? "",
|
|
579
|
+
accountId: accounts.find((account) => account.selected)?.id ?? "",
|
|
537
580
|
accountName: credential.nickname ?? credential.uin ?? credential.uid,
|
|
538
581
|
...credential.uin === void 0 ? {} : { uin: credential.uin },
|
|
539
582
|
...credential.domain === "" ? {} : { domain: credential.domain },
|
|
@@ -554,6 +597,18 @@ async function workBuddyWebStatus(deps) {
|
|
|
554
597
|
};
|
|
555
598
|
}
|
|
556
599
|
/**
|
|
600
|
+
* The region a request addresses, or a 400 answer. Absent parameter means the
|
|
601
|
+
* domestic tab; an unknown value is refused rather than guessed.
|
|
602
|
+
*/
|
|
603
|
+
function requestRegion(req, res) {
|
|
604
|
+
const region = regionOfStatusUrl(req.url ?? "/");
|
|
605
|
+
if (region === void 0) {
|
|
606
|
+
json(res, 400, { error: "unknown region" });
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
return region;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
557
612
|
* Mount the read-only routes on a context where `webServer` is available.
|
|
558
613
|
* The caller uses `ctx.inject(['webServer'], ...)`, so Desktop startup order
|
|
559
614
|
* cannot make this registration disappear.
|
|
@@ -572,8 +627,10 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
572
627
|
json(res, 403, { error: "origin-not-trusted" });
|
|
573
628
|
return;
|
|
574
629
|
}
|
|
630
|
+
const region = requestRegion(req, res);
|
|
631
|
+
if (region === void 0) return;
|
|
575
632
|
try {
|
|
576
|
-
json(res, 200, await workBuddyWebStatus(deps));
|
|
633
|
+
json(res, 200, await workBuddyWebStatus(deps, region));
|
|
577
634
|
} catch (error) {
|
|
578
635
|
json(res, 500, { error: safeMessage(error) });
|
|
579
636
|
}
|
|
@@ -585,8 +642,10 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
585
642
|
handler: async (req, res) => {
|
|
586
643
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
587
644
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
645
|
+
const region = requestRegion(req, res);
|
|
646
|
+
if (region === void 0) return;
|
|
588
647
|
try {
|
|
589
|
-
json(res, 200, { accounts: (await deps.store.accounts()).map(toWebAccount) });
|
|
648
|
+
json(res, 200, { accounts: (await deps.store(region).accounts()).map(toWebAccount) });
|
|
590
649
|
} catch (error) {
|
|
591
650
|
json(res, 500, { error: safeMessage(error) });
|
|
592
651
|
}
|
|
@@ -598,8 +657,10 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
598
657
|
handler: async (req, res) => {
|
|
599
658
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
600
659
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
660
|
+
const region = requestRegion(req, res);
|
|
661
|
+
if (region === void 0) return;
|
|
601
662
|
try {
|
|
602
|
-
const credential = await deps.store.resolve();
|
|
663
|
+
const credential = await deps.store(region).resolve();
|
|
603
664
|
const current = await deps.client.fetchCheckinStatus(credential);
|
|
604
665
|
if (!current.active) return json(res, 409, { error: "check-in activity is not active" });
|
|
605
666
|
if (current.todayCheckedIn) return json(res, 200, {
|
|
@@ -623,9 +684,10 @@ function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
|
623
684
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
624
685
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
625
686
|
if (deps.discoverModels === void 0) return json(res, 503, { error: "model refresh unavailable" });
|
|
687
|
+
const region = requestRegion(req, res);
|
|
688
|
+
if (region === void 0) return;
|
|
626
689
|
try {
|
|
627
|
-
|
|
628
|
-
json(res, 200, { models: (await deps.discoverModels()).map((model) => toWebModel(model, deps.contextBudgets(region))) });
|
|
690
|
+
json(res, 200, { models: (await deps.discoverModels(region)).map((model) => toWebModel(model, deps.contextBudgets(region))) });
|
|
629
691
|
} catch (error) {
|
|
630
692
|
json(res, 500, { error: safeMessage(error) });
|
|
631
693
|
}
|
|
@@ -659,9 +721,14 @@ const regionStateConfig = z.object({
|
|
|
659
721
|
imageModelIds: z.array(z.string()).default([]),
|
|
660
722
|
contextBudgets: z.dict(z.number().step(1).min(1)).default({})
|
|
661
723
|
});
|
|
724
|
+
const accountSelectionConfig = z.object({
|
|
725
|
+
cn: z.string().description("Selected domestic (CN) account id (never a token)"),
|
|
726
|
+
global: z.string().description("Selected international account id (never a token)")
|
|
727
|
+
});
|
|
662
728
|
const Config = z.object({
|
|
663
729
|
authFile: z.string().description("WorkBuddy desktop auth file (defaults to the app's own location)"),
|
|
664
|
-
accountId: z.string().description("
|
|
730
|
+
accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
|
|
731
|
+
accounts: accountSelectionConfig.description("Per-region account selections, keyed cn | global"),
|
|
665
732
|
regions: z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | global"),
|
|
666
733
|
lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
|
|
667
734
|
enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
|
|
@@ -688,26 +755,36 @@ function regionStateOf(config, region) {
|
|
|
688
755
|
...config.contextBudgets === void 0 ? {} : { contextBudgets: config.contextBudgets }
|
|
689
756
|
};
|
|
690
757
|
}
|
|
758
|
+
/** Every region, in card tab order. */
|
|
759
|
+
const REGION_KEYS = ["cn", "global"];
|
|
691
760
|
/**
|
|
692
|
-
* Start
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
761
|
+
* Start both regions' loopback endpoints, register the `workbuddy` (CN) and
|
|
762
|
+
* `workbuddy-global` (international) providers, and refresh each region's
|
|
763
|
+
* model catalog from the upstream once that region's credentials allow it.
|
|
764
|
+
* The static fallback catalogs serve from the first moment, so an offline
|
|
765
|
+
* upstream never leaves a provider empty.
|
|
696
766
|
*/
|
|
697
767
|
function apply(ctx, config) {
|
|
698
768
|
const client = new WorkBuddyUpstreamClient();
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
769
|
+
const stacks = {};
|
|
770
|
+
for (const region of REGION_KEYS) {
|
|
771
|
+
const store = new WorkBuddyCredentialStore({
|
|
772
|
+
region,
|
|
773
|
+
...config.authFile === void 0 ? {} : { desktopPath: config.authFile },
|
|
774
|
+
refresh: (credential) => client.refreshToken(credential)
|
|
775
|
+
});
|
|
776
|
+
const catalog = new WorkBuddyCatalog(region);
|
|
777
|
+
stacks[region] = {
|
|
778
|
+
store,
|
|
779
|
+
catalog,
|
|
780
|
+
shim: createWorkBuddyShim({
|
|
781
|
+
store,
|
|
782
|
+
client,
|
|
783
|
+
catalog,
|
|
784
|
+
logger: ctx.logger
|
|
785
|
+
})
|
|
786
|
+
};
|
|
787
|
+
}
|
|
711
788
|
const withImageSelection = (models, images) => models.map((model) => ({
|
|
712
789
|
...model,
|
|
713
790
|
...images.has(model.id) ? { multimodal: true } : { multimodal: false }
|
|
@@ -723,25 +800,33 @@ function apply(ctx, config) {
|
|
|
723
800
|
let current = () => config;
|
|
724
801
|
let invalidateCatalog = () => {};
|
|
725
802
|
/**
|
|
726
|
-
*
|
|
727
|
-
* the
|
|
728
|
-
*
|
|
729
|
-
*
|
|
803
|
+
* Legacy migration for the pre-split single `accountId`: its region is
|
|
804
|
+
* resolved once from the local account scan and the selection is then
|
|
805
|
+
* attributed to that region ONLY — the other region keeps its documented
|
|
806
|
+
* default (follow the app's current sign-in) instead of silently inheriting
|
|
807
|
+
* a selection that belongs to the other side of the split.
|
|
730
808
|
*/
|
|
731
|
-
let
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
return currentRegion;
|
|
809
|
+
let legacyAccountRegion;
|
|
810
|
+
const effectiveAccountFor = (region, value) => {
|
|
811
|
+
const explicit = value.accounts?.[region];
|
|
812
|
+
if (explicit !== void 0) return explicit;
|
|
813
|
+
return legacyAccountRegion === region ? value.accountId : void 0;
|
|
737
814
|
};
|
|
738
|
-
const discoverModels = async (signal) => {
|
|
739
|
-
const credential = await store.resolve();
|
|
740
|
-
currentRegion = regionOf(credential.domain);
|
|
815
|
+
const discoverModels = async (region, signal) => {
|
|
816
|
+
const credential = await stacks[region].store.resolve();
|
|
741
817
|
return client.fetchModels(credential, signal);
|
|
742
818
|
};
|
|
819
|
+
/** Push the current config into every region's store selection and catalog. */
|
|
820
|
+
const applySelection = (value) => {
|
|
821
|
+
for (const region of REGION_KEYS) {
|
|
822
|
+
stacks[region].store.setDesktopPath(value.authFile);
|
|
823
|
+
stacks[region].store.selectAccount(effectiveAccountFor(region, value));
|
|
824
|
+
stacks[region].catalog.set(configuredModels(value, region));
|
|
825
|
+
}
|
|
826
|
+
invalidateCatalog();
|
|
827
|
+
};
|
|
743
828
|
ctx.inject(["webServer"], (webCtx) => registerWorkBuddyStatusRoute(webCtx, {
|
|
744
|
-
store,
|
|
829
|
+
store: (region) => stacks[region].store,
|
|
745
830
|
client,
|
|
746
831
|
displayModels: (region) => displayModels(current(), region),
|
|
747
832
|
enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
|
|
@@ -754,67 +839,83 @@ function apply(ctx, config) {
|
|
|
754
839
|
current = source;
|
|
755
840
|
},
|
|
756
841
|
onChange() {
|
|
757
|
-
|
|
758
|
-
store.setDesktopPath(next.authFile);
|
|
759
|
-
store.selectAccount(next.accountId);
|
|
760
|
-
catalog.set(configuredModels(next, currentRegion));
|
|
761
|
-
invalidateCatalog();
|
|
762
|
-
regionOfCredential().then((region) => {
|
|
763
|
-
catalog.set(configuredModels(current(), region));
|
|
764
|
-
invalidateCatalog();
|
|
765
|
-
});
|
|
842
|
+
applySelection(current());
|
|
766
843
|
}
|
|
767
844
|
});
|
|
845
|
+
applySelection(config);
|
|
846
|
+
(async () => {
|
|
847
|
+
const id = current().accountId;
|
|
848
|
+
if (id === void 0) return;
|
|
849
|
+
try {
|
|
850
|
+
for (const region of REGION_KEYS) if ((await stacks[region].store.accounts()).some((account) => account.id === id)) {
|
|
851
|
+
legacyAccountRegion = region;
|
|
852
|
+
applySelection(current());
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
} catch {}
|
|
856
|
+
})();
|
|
768
857
|
let stopped = false;
|
|
769
858
|
ctx.effect(() => () => {
|
|
770
859
|
stopped = true;
|
|
771
|
-
shim.close();
|
|
860
|
+
for (const region of REGION_KEYS) stacks[region].shim.close();
|
|
772
861
|
clearHostHeartbeat();
|
|
773
862
|
});
|
|
774
|
-
shim.ready.then(async () => {
|
|
863
|
+
Promise.all(REGION_KEYS.map((region) => stacks[region].shim.ready)).then(async () => {
|
|
775
864
|
if (stopped) return;
|
|
776
|
-
|
|
865
|
+
const adapters = {};
|
|
777
866
|
try {
|
|
778
|
-
const
|
|
779
|
-
shim,
|
|
780
|
-
store,
|
|
781
|
-
catalog,
|
|
867
|
+
for (const region of REGION_KEYS) adapters[region] = createWorkBuddyAdapter({
|
|
868
|
+
shim: stacks[region].shim,
|
|
869
|
+
store: stacks[region].store,
|
|
870
|
+
catalog: stacks[region].catalog,
|
|
871
|
+
provider: WORKBUDDY_PROVIDERS[region],
|
|
872
|
+
displayName: WORKBUDDY_PROVIDER_DISPLAY_NAMES[region],
|
|
782
873
|
resolveAttachments: () => ctx.get("attachments")
|
|
783
874
|
});
|
|
784
|
-
invalidate = workbuddy.invalidate;
|
|
785
875
|
invalidateCatalog = () => {
|
|
786
|
-
|
|
876
|
+
for (const region of REGION_KEYS) adapters[region].invalidate();
|
|
787
877
|
};
|
|
788
|
-
let
|
|
878
|
+
let releaseAdapterCn;
|
|
879
|
+
let releaseAdapterGlobal;
|
|
789
880
|
let releaseDirectory;
|
|
790
881
|
try {
|
|
791
|
-
|
|
882
|
+
releaseAdapterCn = ctx.llm.registerAdapter([WORKBUDDY_PROVIDER], adapters.cn.adapter);
|
|
883
|
+
releaseAdapterGlobal = ctx.llm.registerAdapter([WORKBUDDY_GLOBAL_PROVIDER], adapters.global.adapter);
|
|
792
884
|
releaseDirectory = ctx.llm.registerConfigurableProviders([{
|
|
793
885
|
provider: WORKBUDDY_PROVIDER,
|
|
794
|
-
displayName:
|
|
886
|
+
displayName: WORKBUDDY_PROVIDER_DISPLAY_NAMES.cn,
|
|
887
|
+
settingsNs: WORKBUDDY_SETTINGS_NS,
|
|
888
|
+
settingsPath: [],
|
|
889
|
+
declared: false
|
|
890
|
+
}, {
|
|
891
|
+
provider: WORKBUDDY_GLOBAL_PROVIDER,
|
|
892
|
+
displayName: WORKBUDDY_PROVIDER_DISPLAY_NAMES.global,
|
|
795
893
|
settingsNs: WORKBUDDY_SETTINGS_NS,
|
|
796
894
|
settingsPath: [],
|
|
797
895
|
declared: false
|
|
798
896
|
}]);
|
|
799
897
|
} finally {
|
|
800
|
-
if (
|
|
801
|
-
|
|
898
|
+
if (releaseAdapterCn === void 0 || releaseAdapterGlobal === void 0 || releaseDirectory === void 0) {
|
|
899
|
+
releaseAdapterCn?.();
|
|
900
|
+
releaseAdapterGlobal?.();
|
|
802
901
|
releaseDirectory?.();
|
|
803
902
|
}
|
|
804
903
|
}
|
|
805
904
|
try {
|
|
806
905
|
ctx.effect(() => () => {
|
|
807
|
-
|
|
906
|
+
releaseAdapterCn?.();
|
|
907
|
+
releaseAdapterGlobal?.();
|
|
808
908
|
releaseDirectory?.();
|
|
809
909
|
});
|
|
810
910
|
} catch {
|
|
811
|
-
|
|
911
|
+
releaseAdapterCn?.();
|
|
912
|
+
releaseAdapterGlobal?.();
|
|
812
913
|
releaseDirectory?.();
|
|
813
914
|
}
|
|
814
915
|
ctx.llm.registerModelDiscovery(WORKBUDDY_SETTINGS_NS, async (request, signal) => {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const
|
|
916
|
+
const region = regionOfProvider(request.provider ?? "");
|
|
917
|
+
if (region === void 0) return [];
|
|
918
|
+
const discovered = await discoverModels(region, signal);
|
|
818
919
|
const state = regionStateOf(current(), region);
|
|
819
920
|
return withImageSelection(deriveCatalog(discovered, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])).map((model) => ({
|
|
820
921
|
id: model.id,
|
|
@@ -830,23 +931,22 @@ function apply(ctx, config) {
|
|
|
830
931
|
return;
|
|
831
932
|
}
|
|
832
933
|
if (stopped) return;
|
|
833
|
-
(async () => {
|
|
934
|
+
for (const region of REGION_KEYS) (async () => {
|
|
834
935
|
try {
|
|
835
|
-
const credential = await store.resolve();
|
|
936
|
+
const credential = await stacks[region].store.resolve();
|
|
836
937
|
if (stopped) return;
|
|
837
|
-
currentRegion = regionOf(credential.domain);
|
|
838
938
|
const models = await client.fetchModels(credential);
|
|
839
939
|
if (stopped) return;
|
|
840
|
-
const state = regionStateOf(current(),
|
|
841
|
-
catalog.set(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])));
|
|
842
|
-
invalidate
|
|
940
|
+
const state = regionStateOf(current(), region);
|
|
941
|
+
stacks[region].catalog.set(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])));
|
|
942
|
+
adapters[region].invalidate();
|
|
843
943
|
} catch (error) {
|
|
844
|
-
ctx.logger.warn(
|
|
944
|
+
ctx.logger.warn(`dsh-connect-workbuddy: dynamic ${region} model catalog unavailable; serving the static fallback list`, error);
|
|
845
945
|
}
|
|
846
946
|
})();
|
|
847
947
|
}).catch((error) => {
|
|
848
|
-
ctx.logger.error("dsh-connect-workbuddy: loopback endpoint failed to start;
|
|
948
|
+
ctx.logger.error("dsh-connect-workbuddy: loopback endpoint failed to start; providers not registered", error);
|
|
849
949
|
});
|
|
850
950
|
}
|
|
851
951
|
//#endregion
|
|
852
|
-
export { Config, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, fallbackModelsFor, inject, isHeartbeatProcessAlive, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, regionStateOf, registerWorkBuddyStatusRoute, toPersistedWorkBuddyModel, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
|
952
|
+
export { Config, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, fallbackModelsFor, inject, isHeartbeatProcessAlive, legacyWorkbuddyOwnAuthPath, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, toPersistedWorkBuddyModel, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-workbuddy",
|
|
3
3
|
"displayName": "DSH Connect WorkBuddy",
|
|
4
4
|
"description": "Connect locally signed-in WorkBuddy models to DeepSeek Harness with a read-only credits overview and model management.",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "2.0.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|