dsh-workbuddy-connect 0.7.7 → 0.7.9
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/README.en.md +18 -13
- package/README.md +18 -13
- package/THIRD_PARTY_NOTICES.txt +24 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +114 -33
- package/lib/{host-heartbeat-FytEpUGO.js → host-heartbeat-SJaZ33tR.js} +351 -19
- package/lib/index.d.ts +7 -2
- package/lib/index.js +398 -59
- package/package.json +27 -27
|
@@ -1,11 +1,226 @@
|
|
|
1
|
-
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir, release } from "node:os";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
-
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
5
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
6
4
|
import { createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
6
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
7
7
|
import { execFile, execFileSync } from "node:child_process";
|
|
8
8
|
import { accessSync, constants, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
9
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-atomic-write@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-atomic-write/lib/index.js
|
|
10
|
+
/**
|
|
11
|
+
* Zero-dependency atomic file replacement and writer coordination.
|
|
12
|
+
* `writeFileAtomic` writes a random-suffix sibling with exclusive create and
|
|
13
|
+
* the caller's permission bits, then renames it over the target, so readers
|
|
14
|
+
* observe either the old or the new complete content and a replaced file ends
|
|
15
|
+
* up with exactly the stated mode. `withFileLock` serializes cross-process
|
|
16
|
+
* writers of one file through a `wx`-created `<file>.lock` sibling, so a
|
|
17
|
+
* read-modify-write cycle can never resurrect a state another writer just
|
|
18
|
+
* replaced; readers stay lock-free because the rename commit is atomic. A lock
|
|
19
|
+
* whose recorded holder process no longer exists is taken over.
|
|
20
|
+
* @module @deepseek-ai/dsh-atomic-write
|
|
21
|
+
*/
|
|
22
|
+
const WINDOWS_TRANSIENT_RENAME_ERRORS = /* @__PURE__ */ new Set([
|
|
23
|
+
"EACCES",
|
|
24
|
+
"EBUSY",
|
|
25
|
+
"EPERM"
|
|
26
|
+
]);
|
|
27
|
+
const WINDOWS_RENAME_RETRY_INITIAL_MS = 20;
|
|
28
|
+
const WINDOWS_RENAME_RETRY_MAX_MS = 200;
|
|
29
|
+
const WINDOWS_RENAME_RETRY_LIMIT = 8;
|
|
30
|
+
/** Whether Windows reported temporary interference with an atomic replacement. */
|
|
31
|
+
function isTransientWindowsRenameError(error) {
|
|
32
|
+
if (process.platform !== "win32") return false;
|
|
33
|
+
return WINDOWS_TRANSIENT_RENAME_ERRORS.has(error?.code ?? "");
|
|
34
|
+
}
|
|
35
|
+
/** Replace the target after bounded retries for transient Windows interference. */
|
|
36
|
+
async function renameAtomicTemp(temp, filename) {
|
|
37
|
+
let delay = WINDOWS_RENAME_RETRY_INITIAL_MS;
|
|
38
|
+
for (let retries = 0;; retries += 1) {
|
|
39
|
+
try {
|
|
40
|
+
await rename(temp, filename);
|
|
41
|
+
return;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (!isTransientWindowsRenameError(error)) throw error;
|
|
44
|
+
if (retries >= WINDOWS_RENAME_RETRY_LIMIT) throw error;
|
|
45
|
+
}
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
47
|
+
delay = Math.min(delay * 2, WINDOWS_RENAME_RETRY_MAX_MS);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Replace `filename` with `content` in one atomic step, creating parent
|
|
52
|
+
* directories. The content is first written to a random-suffix sibling opened
|
|
53
|
+
* with exclusive create (`wx`): the open refuses to follow a symlink planted
|
|
54
|
+
* at the temp path, and the fresh inode carries `options.mode` through the
|
|
55
|
+
* rename, so replacing a wider-permission file narrows it without a chmod
|
|
56
|
+
* race. The rename also replaces a symlinked target itself instead of writing
|
|
57
|
+
* through to its referent, and the same-directory sibling keeps the rename on
|
|
58
|
+
* one filesystem. Windows replacement retries transient `EACCES`, `EBUSY`,
|
|
59
|
+
* and `EPERM` failures for a bounded interval while the complete temp file
|
|
60
|
+
* remains the rename source. On any remaining failure the temp file is
|
|
61
|
+
* removed and the failure rethrown. Crash durability (fsync) is out of scope.
|
|
62
|
+
* @param filename - final path receiving the content.
|
|
63
|
+
* @param content - complete next file content.
|
|
64
|
+
* @param options - permission bits for the replacement inode.
|
|
65
|
+
*/
|
|
66
|
+
async function writeFileAtomic(filename, content, options) {
|
|
67
|
+
await mkdir(dirname(filename), {
|
|
68
|
+
recursive: true,
|
|
69
|
+
...options.dirMode === void 0 ? {} : { mode: options.dirMode }
|
|
70
|
+
});
|
|
71
|
+
const temp = `${filename}.${randomBytes(6).toString("hex")}.tmp`;
|
|
72
|
+
try {
|
|
73
|
+
await writeFile(temp, content, {
|
|
74
|
+
mode: options.mode,
|
|
75
|
+
flag: "wx"
|
|
76
|
+
});
|
|
77
|
+
await renameAtomicTemp(temp, filename);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await rm(temp, { force: true });
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Whether an exclusive create found an existing lock. */
|
|
84
|
+
async function isLockContention(error, lockPath) {
|
|
85
|
+
const code = error?.code;
|
|
86
|
+
if (code === "EEXIST") return true;
|
|
87
|
+
if (code !== "EPERM") return false;
|
|
88
|
+
try {
|
|
89
|
+
await lstat(lockPath);
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Whether the holder a `<pid>\n` record names is proven gone: a signal probe finds no such process. */
|
|
96
|
+
function holderExited(record) {
|
|
97
|
+
if (!/^\d+\n$/.test(record)) return false;
|
|
98
|
+
const pid = Number(record.trim());
|
|
99
|
+
if (pid === 0 || pid > 2147483647) return false;
|
|
100
|
+
if (pid === process.pid) return false;
|
|
101
|
+
try {
|
|
102
|
+
process.kill(pid, 0);
|
|
103
|
+
return false;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return error.code === "ESRCH";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** The lock file's content, or undefined when it cannot be read. */
|
|
109
|
+
async function readLockRecord(lockPath) {
|
|
110
|
+
try {
|
|
111
|
+
return await readFile(lockPath, "utf8");
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Remove the lock when its recorded holder exited. Contenders that read the
|
|
118
|
+
* same record serialize on a claim file named after it. Under the claim, the
|
|
119
|
+
* claimant re-reads the lock and probes its PID again, and removes it only
|
|
120
|
+
* when it still holds that record and that PID is still gone: the record's
|
|
121
|
+
* holder can no longer release it, and no other contender can remove it
|
|
122
|
+
* without the claim, so a removal never deletes a lock another contender
|
|
123
|
+
* acquired after the dead holder's, including one whose holder reused the PID.
|
|
124
|
+
* @returns Whether this call removed the dead holder's lock.
|
|
125
|
+
*/
|
|
126
|
+
async function takeOverExitedLock(lockPath) {
|
|
127
|
+
const record = await readLockRecord(lockPath);
|
|
128
|
+
if (record === void 0 || !holderExited(record)) return false;
|
|
129
|
+
const claim = `${lockPath}.takeover-${createHash("sha256").update(record).digest("hex").slice(0, 16)}`;
|
|
130
|
+
try {
|
|
131
|
+
await writeFile(claim, `${process.pid}\n`, {
|
|
132
|
+
mode: 384,
|
|
133
|
+
flag: "wx"
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const code = error.code;
|
|
137
|
+
if (code === "EEXIST" || code === "EPERM") return false;
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
if (await readLockRecord(lockPath) !== record || !holderExited(record)) return false;
|
|
142
|
+
try {
|
|
143
|
+
await rm(lockPath, { force: true });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
} finally {
|
|
149
|
+
await rm(claim, { force: true }).catch((error) => {});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Retry cadence for a contended lock. These stay robustness invariants of the
|
|
154
|
+
* cross-process write protocol rather than deployment tunables: they govern how
|
|
155
|
+
* often a contender asks, which no caller has a reason to vary.
|
|
156
|
+
*/
|
|
157
|
+
const LOCK_RETRY_INITIAL_MS = 20;
|
|
158
|
+
const LOCK_RETRY_MAX_MS = 200;
|
|
159
|
+
/**
|
|
160
|
+
* How long a contender waits when the caller states no limit — sized for the
|
|
161
|
+
* render-and-rename cycle every call site had when this package was written.
|
|
162
|
+
* Expiry fails the contender rather than guessing whether the existing lock
|
|
163
|
+
* still has an owner. How long is *worth* waiting is a property of the
|
|
164
|
+
* operation the lock holder runs, which is why {@link FileLockOptions.waitMs}
|
|
165
|
+
* exists; the value here is the floor for an operation that does file work
|
|
166
|
+
* alone.
|
|
167
|
+
*/
|
|
168
|
+
const DEFAULT_LOCK_WAIT_MS = 2e3;
|
|
169
|
+
/**
|
|
170
|
+
* Hold the cross-process writer lock for `filename` around one operation. The
|
|
171
|
+
* lock is a `wx`-created sibling (`<filename>.lock`); paired with the
|
|
172
|
+
* rename-based commit of {@link writeFileAtomic}, readers stay lock-free and
|
|
173
|
+
* only writers contend. `EEXIST` is contention directly; an `EPERM` is
|
|
174
|
+
* contention only when a fresh `lstat` confirms the lock path exists, covering
|
|
175
|
+
* Windows exclusive-create behavior. Windows retries one unconfirmed EPERM
|
|
176
|
+
* because the holder can release before the probe; a repeated unconfirmed
|
|
177
|
+
* permission error is rethrown. The lock records its holder's PID. A contender
|
|
178
|
+
* removes the lock and retries at once when no process with that PID exists
|
|
179
|
+
* (`ESRCH`); any other lock, including one whose holder exists under another
|
|
180
|
+
* user (`EPERM`) or whose record is incomplete, is waited for. Contention backs
|
|
181
|
+
* off exponentially and times out after the deadline. A holder whose PID a
|
|
182
|
+
* live process reused keeps its lock until an operator removes it. Takeover
|
|
183
|
+
* proves only that the recorded process exited: an operation that starts other
|
|
184
|
+
* writers must stop them with it or leave its successor a way to find them.
|
|
185
|
+
* PIDs are compared on the contender's host, so writers on other hosts or in
|
|
186
|
+
* other PID namespaces sharing the file are unsupported and could both hold
|
|
187
|
+
* the lock. The parent directory must exist.
|
|
188
|
+
* @param filename - the file whose writers this lock serializes.
|
|
189
|
+
* @param operation - the read-render-commit cycle to run while holding the lock.
|
|
190
|
+
* @param options - acquisition options; omitted waits {@link DEFAULT_LOCK_WAIT_MS}.
|
|
191
|
+
* @returns the operation's result; the lock releases on both outcomes.
|
|
192
|
+
*/
|
|
193
|
+
async function withFileLock(filename, operation, options) {
|
|
194
|
+
const lockPath = `${filename}.lock`;
|
|
195
|
+
const deadline = Date.now() + (options?.waitMs ?? DEFAULT_LOCK_WAIT_MS);
|
|
196
|
+
let delay = LOCK_RETRY_INITIAL_MS;
|
|
197
|
+
let retriedUnconfirmedPermissionError = false;
|
|
198
|
+
for (;;) {
|
|
199
|
+
options?.signal?.throwIfAborted();
|
|
200
|
+
try {
|
|
201
|
+
await writeFile(lockPath, `${process.pid}\n`, {
|
|
202
|
+
mode: 384,
|
|
203
|
+
flag: "wx"
|
|
204
|
+
});
|
|
205
|
+
break;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!await isLockContention(error, lockPath)) {
|
|
208
|
+
if (process.platform !== "win32" || error?.code !== "EPERM" || retriedUnconfirmedPermissionError) throw error;
|
|
209
|
+
retriedUnconfirmedPermissionError = true;
|
|
210
|
+
} else if (await takeOverExitedLock(lockPath)) continue;
|
|
211
|
+
}
|
|
212
|
+
if (Date.now() >= deadline) throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath}`);
|
|
213
|
+
await setTimeout$1(delay, void 0, { signal: options?.signal });
|
|
214
|
+
delay = Math.min(delay * 2, LOCK_RETRY_MAX_MS);
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
options?.signal?.throwIfAborted();
|
|
218
|
+
return await operation();
|
|
219
|
+
} finally {
|
|
220
|
+
await rm(lockPath, { force: true });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
9
224
|
//#region src/app-version.ts
|
|
10
225
|
/**
|
|
11
226
|
* The international desktop app's version, used as the `/v3/config` UA.
|
|
@@ -1519,7 +1734,41 @@ function parseModelCatalog(data, international = false, promoBadges) {
|
|
|
1519
1734
|
}
|
|
1520
1735
|
const models = cliIds.map((id) => byId.get(id)).filter((model) => model !== void 0);
|
|
1521
1736
|
if (models.length === 0) throw new Error("workbuddy model catalog resolved to an empty list");
|
|
1522
|
-
return models;
|
|
1737
|
+
return disambiguateDisplayNames(models);
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Make display names unique within one catalog.
|
|
1741
|
+
*
|
|
1742
|
+
* The CN document names both `hy3` and `hy3-x` "Hy3" — two neighbouring rows
|
|
1743
|
+
* whose rates differ (`x0.00` free versus `x0.05`), so the picker and the probe
|
|
1744
|
+
* card render two identical rows and the user cannot tell which one they are
|
|
1745
|
+
* paying for. The static fallback spells the second one "Hy3-X", but the live
|
|
1746
|
+
* fetch overwrote that correction: as soon as the roster gained the id, the
|
|
1747
|
+
* collision came back through the parse path.
|
|
1748
|
+
*
|
|
1749
|
+
* The suffix comes from the id itself — whatever follows the shared name stem
|
|
1750
|
+
* — so `hy3-x` reads "Hy3-X" again. Ids are untouched: they are the wire
|
|
1751
|
+
* contract the request path and the shim route on. Only exact (case-
|
|
1752
|
+
* insensitive) collisions are rewritten; a distinct upstream name such as
|
|
1753
|
+
* `MiniMax-M3` for `minimax-m3-pay` keeps the upstream's own wording.
|
|
1754
|
+
*/
|
|
1755
|
+
function disambiguateDisplayNames(models) {
|
|
1756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1757
|
+
return models.map((model) => {
|
|
1758
|
+
const key = model.name.trim().toLowerCase();
|
|
1759
|
+
if (!seen.has(key)) {
|
|
1760
|
+
seen.add(key);
|
|
1761
|
+
return model;
|
|
1762
|
+
}
|
|
1763
|
+
const tail = model.id.toLowerCase().startsWith(key) ? model.id.slice(key.length) : "";
|
|
1764
|
+
if (tail === "") return model;
|
|
1765
|
+
const name = `${model.name}${tail.toUpperCase()}`;
|
|
1766
|
+
seen.add(name.toLowerCase());
|
|
1767
|
+
return {
|
|
1768
|
+
...model,
|
|
1769
|
+
name
|
|
1770
|
+
};
|
|
1771
|
+
});
|
|
1523
1772
|
}
|
|
1524
1773
|
/** Extract the promotions covering `model` from the `modelPromotions` array. */
|
|
1525
1774
|
function parsePromotions(value, model) {
|
|
@@ -3436,12 +3685,12 @@ var WorkBuddyCredentialStore = class {
|
|
|
3436
3685
|
*/
|
|
3437
3686
|
/**
|
|
3438
3687
|
* Static CLI models observed on the CN endpoint (re-verified against the live
|
|
3439
|
-
* `/v3/config` document 2026-
|
|
3688
|
+
* `/v3/config` document 2026-10-17, including the thinking-effort and billing
|
|
3440
3689
|
* metadata). The upstream refresh replaces this list at startup; it exists so
|
|
3441
3690
|
* the provider registers with a usable catalog even while the first fetch is
|
|
3442
3691
|
* in flight or offline.
|
|
3443
3692
|
*
|
|
3444
|
-
* The list tracks the `cli` agent's model roster exactly — the
|
|
3693
|
+
* The list tracks the `cli` agent's model roster exactly — the 19 models it
|
|
3445
3694
|
* offered that day. The roster churns quickly (`auto`, `kimi-k3-1`,
|
|
3446
3695
|
* `minimax-m3` each appeared or vanished within days, and a competing patch's
|
|
3447
3696
|
* 2026-09-22 snapshot named three ids that were gone a day later), so this
|
|
@@ -3452,6 +3701,11 @@ var WorkBuddyCredentialStore = class {
|
|
|
3452
3701
|
* document, and the `free` flag follows the normalized `x0.00` credits
|
|
3453
3702
|
* marker.
|
|
3454
3703
|
*
|
|
3704
|
+
* Image capability is also verbatim, and it moved since the previous capture:
|
|
3705
|
+
* `glm-5.2` and `glm-5.1` now report `supportsImages: false`. Claiming it for
|
|
3706
|
+
* them admitted an attachment the upstream then rejected after the message was
|
|
3707
|
+
* durable, so this table follows the document rather than the old snapshot.
|
|
3708
|
+
*
|
|
3455
3709
|
* Deliberately NOT baked in: promotional badges. `限时免费` and friends are
|
|
3456
3710
|
* dynamic console-side promotions with no reliable validity window, so a
|
|
3457
3711
|
* static table would keep them alive long after the offers end. The live
|
|
@@ -3463,7 +3717,7 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3463
3717
|
{
|
|
3464
3718
|
id: "hy4-preview",
|
|
3465
3719
|
name: "Hy4 preview",
|
|
3466
|
-
contextWindow:
|
|
3720
|
+
contextWindow: 96e4,
|
|
3467
3721
|
maxTokens: 64e3,
|
|
3468
3722
|
supportsImages: true,
|
|
3469
3723
|
reasoning: {
|
|
@@ -3512,6 +3766,52 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3512
3766
|
free: false
|
|
3513
3767
|
}
|
|
3514
3768
|
},
|
|
3769
|
+
{
|
|
3770
|
+
id: "step-5-preview",
|
|
3771
|
+
name: "Step-5-Preview",
|
|
3772
|
+
contextWindow: 1e6,
|
|
3773
|
+
maxTokens: 64e3,
|
|
3774
|
+
supportsImages: true,
|
|
3775
|
+
reasoning: {
|
|
3776
|
+
supports: true,
|
|
3777
|
+
onlyReasoning: true,
|
|
3778
|
+
supportedEfforts: [
|
|
3779
|
+
"low",
|
|
3780
|
+
"medium",
|
|
3781
|
+
"high"
|
|
3782
|
+
],
|
|
3783
|
+
defaultEffort: "high",
|
|
3784
|
+
canDisableThinking: false
|
|
3785
|
+
},
|
|
3786
|
+
billing: {
|
|
3787
|
+
credits: "x0.00 credits",
|
|
3788
|
+
free: true
|
|
3789
|
+
}
|
|
3790
|
+
},
|
|
3791
|
+
{
|
|
3792
|
+
id: "space-bunny",
|
|
3793
|
+
name: "Space-Bunny",
|
|
3794
|
+
contextWindow: 1e6,
|
|
3795
|
+
maxTokens: 128e3,
|
|
3796
|
+
supportsImages: true,
|
|
3797
|
+
reasoning: {
|
|
3798
|
+
supports: true,
|
|
3799
|
+
onlyReasoning: true,
|
|
3800
|
+
supportedEfforts: [
|
|
3801
|
+
"low",
|
|
3802
|
+
"medium",
|
|
3803
|
+
"high",
|
|
3804
|
+
"xhigh",
|
|
3805
|
+
"max"
|
|
3806
|
+
],
|
|
3807
|
+
defaultEffort: "max",
|
|
3808
|
+
canDisableThinking: false
|
|
3809
|
+
},
|
|
3810
|
+
billing: {
|
|
3811
|
+
credits: "x0.08 credits",
|
|
3812
|
+
free: false
|
|
3813
|
+
}
|
|
3814
|
+
},
|
|
3515
3815
|
{
|
|
3516
3816
|
id: "deepseek-v4.1-flash",
|
|
3517
3817
|
name: "Deepseek-V4.1-Flash",
|
|
@@ -3521,11 +3821,16 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3521
3821
|
reasoning: {
|
|
3522
3822
|
supports: true,
|
|
3523
3823
|
onlyReasoning: true,
|
|
3824
|
+
supportedEfforts: [
|
|
3825
|
+
"low",
|
|
3826
|
+
"high",
|
|
3827
|
+
"max"
|
|
3828
|
+
],
|
|
3524
3829
|
defaultEffort: "high",
|
|
3525
|
-
canDisableThinking:
|
|
3830
|
+
canDisableThinking: true
|
|
3526
3831
|
},
|
|
3527
3832
|
billing: {
|
|
3528
|
-
credits: "x0.
|
|
3833
|
+
credits: "x0.11 credits",
|
|
3529
3834
|
free: false
|
|
3530
3835
|
}
|
|
3531
3836
|
},
|
|
@@ -3538,7 +3843,12 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3538
3843
|
reasoning: {
|
|
3539
3844
|
supports: true,
|
|
3540
3845
|
onlyReasoning: true,
|
|
3541
|
-
|
|
3846
|
+
supportedEfforts: [
|
|
3847
|
+
"low",
|
|
3848
|
+
"high",
|
|
3849
|
+
"max"
|
|
3850
|
+
],
|
|
3851
|
+
defaultEffort: "max",
|
|
3542
3852
|
canDisableThinking: false
|
|
3543
3853
|
},
|
|
3544
3854
|
billing: {
|
|
@@ -3561,19 +3871,41 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3561
3871
|
"max"
|
|
3562
3872
|
],
|
|
3563
3873
|
defaultEffort: "high",
|
|
3564
|
-
canDisableThinking:
|
|
3874
|
+
canDisableThinking: false
|
|
3565
3875
|
},
|
|
3566
3876
|
billing: {
|
|
3567
3877
|
credits: "x0.06 credits",
|
|
3568
3878
|
free: false
|
|
3569
3879
|
}
|
|
3570
3880
|
},
|
|
3881
|
+
{
|
|
3882
|
+
id: "glm-5.3-flashx",
|
|
3883
|
+
name: "GLM-5.3-FlashX",
|
|
3884
|
+
contextWindow: 1e6,
|
|
3885
|
+
maxTokens: 131072,
|
|
3886
|
+
supportsImages: true,
|
|
3887
|
+
reasoning: {
|
|
3888
|
+
supports: true,
|
|
3889
|
+
onlyReasoning: true,
|
|
3890
|
+
supportedEfforts: [
|
|
3891
|
+
"low",
|
|
3892
|
+
"high",
|
|
3893
|
+
"max"
|
|
3894
|
+
],
|
|
3895
|
+
defaultEffort: "high",
|
|
3896
|
+
canDisableThinking: false
|
|
3897
|
+
},
|
|
3898
|
+
billing: {
|
|
3899
|
+
credits: "x0.14 credits",
|
|
3900
|
+
free: false
|
|
3901
|
+
}
|
|
3902
|
+
},
|
|
3571
3903
|
{
|
|
3572
3904
|
id: "glm-5.2",
|
|
3573
3905
|
name: "GLM-5.2",
|
|
3574
3906
|
contextWindow: 1e6,
|
|
3575
3907
|
maxTokens: 64e3,
|
|
3576
|
-
supportsImages:
|
|
3908
|
+
supportsImages: false,
|
|
3577
3909
|
reasoning: {
|
|
3578
3910
|
supports: true,
|
|
3579
3911
|
onlyReasoning: true,
|
|
@@ -3590,7 +3922,7 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3590
3922
|
name: "GLM-5.1",
|
|
3591
3923
|
contextWindow: 2e5,
|
|
3592
3924
|
maxTokens: 48e3,
|
|
3593
|
-
supportsImages:
|
|
3925
|
+
supportsImages: false,
|
|
3594
3926
|
reasoning: {
|
|
3595
3927
|
supports: true,
|
|
3596
3928
|
onlyReasoning: true,
|
|
@@ -3620,10 +3952,10 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3620
3952
|
}
|
|
3621
3953
|
},
|
|
3622
3954
|
{
|
|
3623
|
-
id: "minimax-m3",
|
|
3955
|
+
id: "minimax-m3-pay",
|
|
3624
3956
|
name: "MiniMax-M3",
|
|
3625
3957
|
contextWindow: 512e3,
|
|
3626
|
-
maxTokens:
|
|
3958
|
+
maxTokens: 128e3,
|
|
3627
3959
|
supportsImages: true,
|
|
3628
3960
|
reasoning: {
|
|
3629
3961
|
supports: true,
|
|
@@ -3649,12 +3981,12 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3649
3981
|
canDisableThinking: false
|
|
3650
3982
|
},
|
|
3651
3983
|
billing: {
|
|
3652
|
-
credits: "x0.
|
|
3984
|
+
credits: "x0.26 credits",
|
|
3653
3985
|
free: false
|
|
3654
3986
|
}
|
|
3655
3987
|
},
|
|
3656
3988
|
{
|
|
3657
|
-
id: "kimi-k3-
|
|
3989
|
+
id: "kimi-k3-2",
|
|
3658
3990
|
name: "Kimi-K3",
|
|
3659
3991
|
contextWindow: 1e6,
|
|
3660
3992
|
maxTokens: 32e3,
|
|
@@ -3666,7 +3998,7 @@ const FALLBACK_WORKBUDDY_MODELS = [
|
|
|
3666
3998
|
canDisableThinking: false
|
|
3667
3999
|
},
|
|
3668
4000
|
billing: {
|
|
3669
|
-
credits: "x1.62
|
|
4001
|
+
credits: "x1.62",
|
|
3670
4002
|
free: false
|
|
3671
4003
|
}
|
|
3672
4004
|
},
|
|
@@ -4210,7 +4542,7 @@ var WorkBuddyCatalog = class {
|
|
|
4210
4542
|
};
|
|
4211
4543
|
//#endregion
|
|
4212
4544
|
//#region src/version.ts
|
|
4213
|
-
const WORKBUDDY_CONNECT_VERSION = "0.7.
|
|
4545
|
+
const WORKBUDDY_CONNECT_VERSION = "0.7.9";
|
|
4214
4546
|
//#endregion
|
|
4215
4547
|
//#region src/host-heartbeat.ts
|
|
4216
4548
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -1129,12 +1129,12 @@ declare class WorkBuddyCredentialStore {
|
|
|
1129
1129
|
type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
|
|
1130
1130
|
/**
|
|
1131
1131
|
* Static CLI models observed on the CN endpoint (re-verified against the live
|
|
1132
|
-
* `/v3/config` document 2026-
|
|
1132
|
+
* `/v3/config` document 2026-10-17, including the thinking-effort and billing
|
|
1133
1133
|
* metadata). The upstream refresh replaces this list at startup; it exists so
|
|
1134
1134
|
* the provider registers with a usable catalog even while the first fetch is
|
|
1135
1135
|
* in flight or offline.
|
|
1136
1136
|
*
|
|
1137
|
-
* The list tracks the `cli` agent's model roster exactly — the
|
|
1137
|
+
* The list tracks the `cli` agent's model roster exactly — the 19 models it
|
|
1138
1138
|
* offered that day. The roster churns quickly (`auto`, `kimi-k3-1`,
|
|
1139
1139
|
* `minimax-m3` each appeared or vanished within days, and a competing patch's
|
|
1140
1140
|
* 2026-09-22 snapshot named three ids that were gone a day later), so this
|
|
@@ -1145,6 +1145,11 @@ type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
|
|
|
1145
1145
|
* document, and the `free` flag follows the normalized `x0.00` credits
|
|
1146
1146
|
* marker.
|
|
1147
1147
|
*
|
|
1148
|
+
* Image capability is also verbatim, and it moved since the previous capture:
|
|
1149
|
+
* `glm-5.2` and `glm-5.1` now report `supportsImages: false`. Claiming it for
|
|
1150
|
+
* them admitted an attachment the upstream then rejected after the message was
|
|
1151
|
+
* durable, so this table follows the document rather than the old snapshot.
|
|
1152
|
+
*
|
|
1148
1153
|
* Deliberately NOT baked in: promotional badges. `限时免费` and friends are
|
|
1149
1154
|
* dynamic console-side promotions with no reliable validity window, so a
|
|
1150
1155
|
* static table would keep them alive long after the offers end. The live
|