dsh-workbuddy-connect 0.7.0 → 0.7.2
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 +53 -13
- package/README.md +47 -13
- package/lib/bin.js +1 -1
- package/lib/client.js +253 -32
- package/lib/{host-heartbeat-d_lBAk8t.js → host-heartbeat-DJb8l_4O.js} +362 -35
- package/lib/index.d.ts +85 -2
- package/lib/index.js +97 -7
- package/package.json +12 -10
|
@@ -387,15 +387,36 @@ function randomSentinel() {
|
|
|
387
387
|
return `probe_sentinel_${randomBytes(12).toString("hex")}`;
|
|
388
388
|
}
|
|
389
389
|
/**
|
|
390
|
-
* The upstream's "this effort value is not supported"
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
390
|
+
* The upstream's "this effort value is not supported" codes, per region, as
|
|
391
|
+
* measured on each live endpoint. The sets are kept separate so a code only
|
|
392
|
+
* ever widens detection for the endpoint it was measured on.
|
|
393
|
+
*
|
|
394
|
+
* - `invalid_reasoning_effort` — the China endpoint, measured 2026-09-11
|
|
395
|
+
* (plan §4.2). Also kept for `global` as a fallback spelling.
|
|
396
|
+
* - `model_param_invalid` — the global endpoint, measured 2026-10-01. A
|
|
397
|
+
* non-canonical value is answered `400` / `11133` with this code, which names
|
|
398
|
+
* no field (`extError.param` is empty), instead of one that names the effort.
|
|
399
|
+
* It is generic enough to be readable here only because the baseline step has
|
|
400
|
+
* already proved the *same* request without `reasoning_effort` succeeds,
|
|
401
|
+
* leaving the sentinel as the only difference between the two attempts. A
|
|
402
|
+
* sibling code in the same `11133` envelope that names another parameter
|
|
403
|
+
* (`integer_below_min_value`, the `max_tokens` floor) and a body carrying no
|
|
404
|
+
* `extError` at all (`11102`, unknown model) are therefore not mistaken for
|
|
405
|
+
* it, and neither is a level-sweep answer. It is *not* added to `cn`: that
|
|
406
|
+
* endpoint was measured answering the specific code, and reading a generic
|
|
407
|
+
* code there would widen attribution beyond what was observed.
|
|
408
|
+
*
|
|
409
|
+
* Neither is treated as a permanent protocol promise: anything unrecognized
|
|
410
|
+
* still degrades to `unknown` rather than to a capability conclusion.
|
|
394
411
|
*/
|
|
395
|
-
const
|
|
412
|
+
const INVALID_EFFORT_CODES = {
|
|
413
|
+
cn: /* @__PURE__ */ new Set(["invalid_reasoning_effort"]),
|
|
414
|
+
global: /* @__PURE__ */ new Set(["invalid_reasoning_effort", "model_param_invalid"])
|
|
415
|
+
};
|
|
396
416
|
/** Whether an attempt is an attributable rejection of the effort value. */
|
|
397
|
-
function isEffortRejection(attempt) {
|
|
398
|
-
|
|
417
|
+
function isEffortRejection(attempt, region) {
|
|
418
|
+
const codes = INVALID_EFFORT_CODES[region];
|
|
419
|
+
return attempt.status === 400 && attempt.errorCode !== void 0 && codes.has(attempt.errorCode);
|
|
399
420
|
}
|
|
400
421
|
/** Whether an attempt shows the upstream accepted the request and streamed. */
|
|
401
422
|
function isAcceptance(attempt) {
|
|
@@ -411,12 +432,15 @@ function unknownReason(stage, attempt) {
|
|
|
411
432
|
* Probe one model.
|
|
412
433
|
*
|
|
413
434
|
* `options.candidates` exists so tests can shorten the sweep; production always
|
|
414
|
-
* uses {@link PROBE_EFFORT_CANDIDATES}.
|
|
435
|
+
* uses {@link PROBE_EFFORT_CANDIDATES}. `options.region` selects which
|
|
436
|
+
* endpoint's rejection vocabulary is read; it defaults to `cn`, which is also
|
|
437
|
+
* the production default for the China app.
|
|
415
438
|
*/
|
|
416
439
|
async function probeModel(options) {
|
|
417
440
|
const sentinel = options.sentinel ?? randomSentinel;
|
|
418
441
|
const candidates = options.candidates ?? PROBE_EFFORT_CANDIDATES;
|
|
419
442
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
443
|
+
const region = options.region ?? "cn";
|
|
420
444
|
let requests = 0;
|
|
421
445
|
const attempt = async (effort) => {
|
|
422
446
|
requests += 1;
|
|
@@ -447,7 +471,7 @@ async function probeModel(options) {
|
|
|
447
471
|
efforts: [],
|
|
448
472
|
requests
|
|
449
473
|
};
|
|
450
|
-
if (!isEffortRejection(sentinelAttempt)) return {
|
|
474
|
+
if (!isEffortRejection(sentinelAttempt, region)) return {
|
|
451
475
|
validation: "unknown",
|
|
452
476
|
efforts: [],
|
|
453
477
|
requests,
|
|
@@ -460,7 +484,7 @@ async function probeModel(options) {
|
|
|
460
484
|
accepted.push(effort);
|
|
461
485
|
continue;
|
|
462
486
|
}
|
|
463
|
-
if (isEffortRejection(levelAttempt)) continue;
|
|
487
|
+
if (isEffortRejection(levelAttempt, region)) continue;
|
|
464
488
|
return {
|
|
465
489
|
validation: "unknown",
|
|
466
490
|
efforts: [],
|
|
@@ -822,6 +846,95 @@ function envelopeError(status, envelope) {
|
|
|
822
846
|
return /* @__PURE__ */ new Error(`workbuddy upstream ${kind} (http ${status}): ${envelope.msg.slice(0, 160)}`);
|
|
823
847
|
}
|
|
824
848
|
/**
|
|
849
|
+
* Parse the billing response's `CycleEndTime` into epoch milliseconds.
|
|
850
|
+
*
|
|
851
|
+
* The upstream sends a zone-less timestamp (`YYYY-MM-DD HH:mm:ss`). This reads it
|
|
852
|
+
* as the running machine's local time, which is what the official desktop client
|
|
853
|
+
* does — it feeds the same field straight to `new Date(value).getTime()`. That
|
|
854
|
+
* makes the plugin agree with the app on the same machine; it is **not** evidence
|
|
855
|
+
* of which zone the server or the account uses, which remains unestablished.
|
|
856
|
+
*
|
|
857
|
+
* A naive read does mean a machine in a different zone than the account's gets a
|
|
858
|
+
* shifted instant. That is accepted here rather than guessed at: inventing an
|
|
859
|
+
* offset would be asserting a zone nobody has verified.
|
|
860
|
+
*
|
|
861
|
+
* @returns Epoch milliseconds, or undefined when absent, malformed, or not a real
|
|
862
|
+
* calendar moment (see the round-trip check below).
|
|
863
|
+
*/
|
|
864
|
+
function parseCycleEndTime(value) {
|
|
865
|
+
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
866
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/u.exec(value.trim());
|
|
867
|
+
if (match === null) return void 0;
|
|
868
|
+
const [, y, mo, d, h, mi, s] = match;
|
|
869
|
+
const [year, month, day, hour, minute, second] = [
|
|
870
|
+
y,
|
|
871
|
+
mo,
|
|
872
|
+
d,
|
|
873
|
+
h,
|
|
874
|
+
mi,
|
|
875
|
+
s
|
|
876
|
+
].map(Number);
|
|
877
|
+
const date = new Date(year, month - 1, day, hour, minute, second);
|
|
878
|
+
const parsed = date.getTime();
|
|
879
|
+
if (!Number.isFinite(parsed)) return void 0;
|
|
880
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day || date.getHours() !== hour || date.getMinutes() !== minute || date.getSeconds() !== second) return;
|
|
881
|
+
return parsed;
|
|
882
|
+
}
|
|
883
|
+
/** Empty bucket, used whenever no account qualifies. */
|
|
884
|
+
const NO_EXPIRING_CREDIT = {
|
|
885
|
+
total: 0,
|
|
886
|
+
packages: 0
|
|
887
|
+
};
|
|
888
|
+
/**
|
|
889
|
+
* Sum the credit lapsing within `horizonMs` of `nowMs`.
|
|
890
|
+
*
|
|
891
|
+
* Only positive balances count: a package with nothing left is not a loss, and
|
|
892
|
+
* including it would make every long-lived account look permanently at risk.
|
|
893
|
+
* Only *future* expiries count too — a package already past its end time would
|
|
894
|
+
* otherwise satisfy every forward horizon at once (`end - now` is negative,
|
|
895
|
+
* hence "within 24 hours"), making a long-dead package look permanently
|
|
896
|
+
* imminent. See {@link WorkBuddyCreditExpiry} for why the past is not reported.
|
|
897
|
+
*/
|
|
898
|
+
function expiringWithin(accounts, nowMs, horizonMs) {
|
|
899
|
+
return sumExpiring(accounts, (end) => end > nowMs && end - nowMs <= horizonMs);
|
|
900
|
+
}
|
|
901
|
+
/** Shared accumulator over accounts whose end time passes `matches`. */
|
|
902
|
+
function sumExpiring(accounts, matches) {
|
|
903
|
+
let total = 0;
|
|
904
|
+
let packages = 0;
|
|
905
|
+
let earliestEndTimeMs;
|
|
906
|
+
for (const account of accounts) {
|
|
907
|
+
const end = account.endTimeMs;
|
|
908
|
+
if (end === void 0 || account.remain <= 0) continue;
|
|
909
|
+
if (!matches(end)) continue;
|
|
910
|
+
total += account.remain;
|
|
911
|
+
packages += 1;
|
|
912
|
+
if (earliestEndTimeMs === void 0 || end < earliestEndTimeMs) earliestEndTimeMs = end;
|
|
913
|
+
}
|
|
914
|
+
if (packages === 0) return NO_EXPIRING_CREDIT;
|
|
915
|
+
return {
|
|
916
|
+
total,
|
|
917
|
+
packages,
|
|
918
|
+
...earliestEndTimeMs === void 0 ? {} : { earliestEndTimeMs }
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Bucket an account's credit by how soon it lapses.
|
|
923
|
+
*
|
|
924
|
+
* Returns undefined when no account carried a usable end time, so a caller can
|
|
925
|
+
* tell "nothing is expiring" apart from "we never learned when anything
|
|
926
|
+
* expires" — the second must not render as reassurance.
|
|
927
|
+
*/
|
|
928
|
+
function summariseExpiry(accounts, nowMs) {
|
|
929
|
+
if (!accounts.some((account) => account.endTimeMs !== void 0)) return {};
|
|
930
|
+
const DAY = 864e5;
|
|
931
|
+
return { expiry: {
|
|
932
|
+
within24h: expiringWithin(accounts, nowMs, DAY),
|
|
933
|
+
within3d: expiringWithin(accounts, nowMs, 3 * DAY),
|
|
934
|
+
within7d: expiringWithin(accounts, nowMs, 7 * DAY)
|
|
935
|
+
} };
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
825
938
|
* Upstream HTTP client. One instance serves the whole plugin; requests take
|
|
826
939
|
* the credential explicitly so token refreshes apply on the next call.
|
|
827
940
|
*
|
|
@@ -863,7 +976,7 @@ var WorkBuddyUpstreamClient = class {
|
|
|
863
976
|
...chatHeaders(credential, userAgent, identity.clientVersion),
|
|
864
977
|
"Authorization": `Bearer ${credential.accessToken}`
|
|
865
978
|
},
|
|
866
|
-
body: region === "global" ? prepareInternationalChatBody(bodyJson) : bodyJson,
|
|
979
|
+
body: region === "global" ? prepareInternationalChatBody(bodyJson) : prepareCnChatBody(bodyJson),
|
|
867
980
|
...signal === void 0 ? {} : { signal }
|
|
868
981
|
});
|
|
869
982
|
} catch (error) {
|
|
@@ -1066,15 +1179,18 @@ var WorkBuddyUpstreamClient = class {
|
|
|
1066
1179
|
else remain = capacityRemain;
|
|
1067
1180
|
if (remain < 0) remain = 0;
|
|
1068
1181
|
total += remain;
|
|
1182
|
+
const endTimeMs = parseCycleEndTime(account["CycleEndTime"]);
|
|
1069
1183
|
accounts.push({
|
|
1070
1184
|
packageName: typeof account["PackageName"] === "string" ? account["PackageName"] : "(unnamed)",
|
|
1071
1185
|
remain,
|
|
1072
|
-
size: size > 0 ? size : numberField("CapacitySize")
|
|
1186
|
+
size: size > 0 ? size : numberField("CapacitySize"),
|
|
1187
|
+
...endTimeMs === void 0 ? {} : { endTimeMs }
|
|
1073
1188
|
});
|
|
1074
1189
|
}
|
|
1075
1190
|
return {
|
|
1076
1191
|
total,
|
|
1077
|
-
accounts
|
|
1192
|
+
accounts,
|
|
1193
|
+
...summariseExpiry(accounts, now.getTime())
|
|
1078
1194
|
};
|
|
1079
1195
|
}
|
|
1080
1196
|
/**
|
|
@@ -1411,6 +1527,32 @@ function modelWithCurrentPromotion(model, now = Date.now()) {
|
|
|
1411
1527
|
* JSON object is returned unchanged, exactly as {@link prepareChatBody} does,
|
|
1412
1528
|
* so this is safe to run over an already-prepared-or-not body.
|
|
1413
1529
|
*/
|
|
1530
|
+
/**
|
|
1531
|
+
* Prepare a body for the **CN** endpoint.
|
|
1532
|
+
*
|
|
1533
|
+
* Until #87 the CN wire was the caller's body verbatim. The only difference
|
|
1534
|
+
* now is the shared {@link dropUnsupportedEffort} strip, whose region scope was
|
|
1535
|
+
* lifted once measurements showed this endpoint rejects `off` too.
|
|
1536
|
+
*
|
|
1537
|
+
* Deliberately **not** routed through {@link prepareChatBody}: the shim already
|
|
1538
|
+
* applies it before handing the body to {@link WorkBuddyUpstreamClient.chatStream},
|
|
1539
|
+
* and re-running it would be a second `stream`/`developer`/`tool_choice`
|
|
1540
|
+
* normalisation of an already-normalised body. Only the strip belongs here.
|
|
1541
|
+
*
|
|
1542
|
+
* A body that is not a JSON object is returned unchanged, so this is safe to
|
|
1543
|
+
* run over an already-prepared-or-not body.
|
|
1544
|
+
*/
|
|
1545
|
+
function prepareCnChatBody(source) {
|
|
1546
|
+
let body;
|
|
1547
|
+
try {
|
|
1548
|
+
body = JSON.parse(source);
|
|
1549
|
+
} catch {
|
|
1550
|
+
return source;
|
|
1551
|
+
}
|
|
1552
|
+
if (!isObject(body)) return source;
|
|
1553
|
+
dropUnsupportedEffort(body);
|
|
1554
|
+
return JSON.stringify(body);
|
|
1555
|
+
}
|
|
1414
1556
|
function prepareInternationalChatBody(source) {
|
|
1415
1557
|
const prepared = prepareChatBody(source);
|
|
1416
1558
|
let body;
|
|
@@ -1432,25 +1574,34 @@ function prepareInternationalChatBody(source) {
|
|
|
1432
1574
|
return JSON.stringify(body);
|
|
1433
1575
|
}
|
|
1434
1576
|
/**
|
|
1435
|
-
* Remove the adapter's own `off` effort spelling from the
|
|
1577
|
+
* Remove the adapter's own `off` effort spelling from the wire, on every region.
|
|
1436
1578
|
*
|
|
1437
1579
|
* `thinkingLevelMap.off` is pinned to the literal `'off'` for models that
|
|
1438
1580
|
* declare `canDisableThinking`, so that the level stays selectable. pi-ai
|
|
1439
|
-
* sends that value for any request carrying no explicit level
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1444
|
-
* `
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1581
|
+
* sends that value for any request carrying no explicit level — which is why
|
|
1582
|
+
* this covers **Default and Off alike**: the two produce the same body, so a
|
|
1583
|
+
* fix that only removed the menu entry would leave Default failing.
|
|
1584
|
+
*
|
|
1585
|
+
* Both endpoints reject it, which is why this is no longer region-scoped
|
|
1586
|
+
* (#87): the international GPT family answers HTTP 400 `11133` /
|
|
1587
|
+
* `extError.param === 'reasoning.effort'` (issue #49), and the CN endpoint
|
|
1588
|
+
* rejects it on models that declare `onlyReasoning` — measured on
|
|
1589
|
+
* `deepseek-v4.1-flash` with 400 / `11150`, while `low`/`high`/`max` pass
|
|
1590
|
+
* (issue #87). Omission is the only form measured good on every such model; a
|
|
1591
|
+
* literal `'none'` is *not* a safe substitute — accepted by the GPT-5.6 family
|
|
1592
|
+
* and GLM, rejected by `gpt-6-astra`.
|
|
1593
|
+
*
|
|
1594
|
+
* Consequences, stated honestly: the picker still offers Off for models where
|
|
1595
|
+
* the upstream declares `canDisableThinking`, but selecting it now means "the
|
|
1596
|
+
* field is omitted" — the model's actual behaviour is decided upstream and is
|
|
1597
|
+
* *not* guaranteed to disable thinking or to match the catalog's
|
|
1598
|
+
* `defaultEffort`. Declared spellings (`low`/`medium`/`high`/`xhigh`/`max`)
|
|
1599
|
+
* and an explicit `none` pass through untouched.
|
|
1600
|
+
*
|
|
1601
|
+
* The CN strip replaces a scope decision whose premise did not hold: the
|
|
1602
|
+
* earlier note here said that endpoint "has accepted this spelling in every
|
|
1603
|
+
* measurement so far". Measurements since (#87) show it rejects `off` on
|
|
1604
|
+
* validating models — the earlier runs had simply not hit one.
|
|
1454
1605
|
*/
|
|
1455
1606
|
function dropUnsupportedEffort(obj) {
|
|
1456
1607
|
if (obj["reasoning_effort"] === "off") delete obj["reasoning_effort"];
|
|
@@ -1850,6 +2001,60 @@ const WINDOWS_REGISTRY_ROOTS = [
|
|
|
1850
2001
|
];
|
|
1851
2002
|
const WINDOWS_REGISTRY_OUTPUT_MAX_BYTES = 1048576;
|
|
1852
2003
|
const WINDOWS_ELECTRON_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u;
|
|
2004
|
+
/**
|
|
2005
|
+
* The PowerShell command that re-reads one uninstall root as UTF-8.
|
|
2006
|
+
*
|
|
2007
|
+
* Used *only* as a fallback, and only to supply data: the caller feeds its
|
|
2008
|
+
* output through the same {@link parseWindowsRegistryOutput} and
|
|
2009
|
+
* {@link inspectWindowsElectronCandidate} as the `reg.exe` path, so candidate
|
|
2010
|
+
* validation, uniqueness, and the "we could not check" rules are unchanged.
|
|
2011
|
+
*
|
|
2012
|
+
* Why a different tool rather than a smarter decode: `reg.exe` writes in a
|
|
2013
|
+
* code page chosen by the console it inherits, and no inspection of the
|
|
2014
|
+
* resulting bytes can recover which one. .NET's registry API returns strings,
|
|
2015
|
+
* so the question never arises.
|
|
2016
|
+
*
|
|
2017
|
+
* Structure notes, each one a defect fixed after review:
|
|
2018
|
+
*
|
|
2019
|
+
* - **`param` is the first statement.** A `param` block must lead the script
|
|
2020
|
+
* block; putting the encoding assignment before it is a parse error in
|
|
2021
|
+
* Windows PowerShell 5.1.
|
|
2022
|
+
* - **`[Console]::OutputEncoding` is set explicitly.** Windows PowerShell 5.1
|
|
2023
|
+
* encodes redirected stdout with it, so UTF-8 is *defined* for this child
|
|
2024
|
+
* rather than inferred from what comes back.
|
|
2025
|
+
* - **Exit codes distinguish "root absent" from "root unreadable"** (`2` and
|
|
2026
|
+
* `3`). The caller branches on the code, never on message text, so the
|
|
2027
|
+
* localized diagnostics below cannot change an outcome.
|
|
2028
|
+
* - **An unopenable subkey is `3`, not `continue`.** Skipping it silently
|
|
2029
|
+
* could drop a real candidate and make the product look absent, which is
|
|
2030
|
+
* precisely the confusion the caller's `incomplete` state exists to prevent.
|
|
2031
|
+
* - **Output is JSON.** PowerShell does the escaping, so paths containing
|
|
2032
|
+
* spaces, commas, or quotes no longer depend on column positions, and only
|
|
2033
|
+
* the two values this plugin reads are collected.
|
|
2034
|
+
*
|
|
2035
|
+
* The root is passed as a `-Root` argument, never interpolated into the
|
|
2036
|
+
* command text.
|
|
2037
|
+
*/
|
|
2038
|
+
const WINDOWS_REGISTRY_RELATIVE_POWERSHELL = "System32\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
2039
|
+
/** Exit code meaning the root key does not exist (a normal, expected outcome). */
|
|
2040
|
+
const WINDOWS_POWERSHELL_ROOT_ABSENT = 2;
|
|
2041
|
+
/** Exit code meaning the root exists but a part of it could not be read. */
|
|
2042
|
+
const WINDOWS_POWERSHELL_ROOT_UNREADABLE = 3;
|
|
2043
|
+
const WINDOWS_POWERSHELL_QUERY_SCRIPT = [
|
|
2044
|
+
"param([string]$Root)",
|
|
2045
|
+
"[Console]::OutputEncoding=[Text.Encoding]::UTF8;",
|
|
2046
|
+
"$ErrorActionPreference=\"Stop\";",
|
|
2047
|
+
"$path=\"Registry::\"+$Root;",
|
|
2048
|
+
"if (-not (Test-Path -LiteralPath $path)) { [Console]::Error.Write(\"root absent\"); exit 2 };",
|
|
2049
|
+
"try { $k=Get-Item -LiteralPath $path -ErrorAction Stop } catch { [Console]::Error.Write(\"root unreadable\"); exit 3 };",
|
|
2050
|
+
"$out=@();",
|
|
2051
|
+
"foreach ($n in $k.GetSubKeyNames()) {",
|
|
2052
|
+
" try { $p=$k.OpenSubKey($n); if ($null -eq $p) { throw \"null\" } } catch { [Console]::Error.Write(\"subkey unreadable\"); exit 3 };",
|
|
2053
|
+
" $d=$p.GetValue(\"DisplayName\");$i=$p.GetValue(\"DisplayIcon\");",
|
|
2054
|
+
" if ($null -ne $d -or $null -ne $i) { $out += [pscustomobject]@{ displayName=[string]$d; displayIcon=[string]$i } }",
|
|
2055
|
+
"};",
|
|
2056
|
+
"ConvertTo-Json -InputObject @($out) -Compress -Depth 3"
|
|
2057
|
+
].join(" ");
|
|
1853
2058
|
/** One discovery subprocess's own limits; see {@link WorkBuddyAtRestKeyProviderOptions}. */
|
|
1854
2059
|
const WORKBUDDY_DISCOVERY_STEP_TIMEOUT_MS = 3e3;
|
|
1855
2060
|
const MDFIND_MAX_OUTPUT_BYTES = 1048576;
|
|
@@ -1944,6 +2149,15 @@ function workBuddyDiscoveryTools(bundleId) {
|
|
|
1944
2149
|
function workBuddyWindowsDiscoveryTools() {
|
|
1945
2150
|
const systemRoot = process.env.SystemRoot?.trim();
|
|
1946
2151
|
const regPath = systemRoot === void 0 || systemRoot === "" ? void 0 : join(systemRoot, "System32", "reg.exe");
|
|
2152
|
+
/**
|
|
2153
|
+
* The fallback interpreter. Resolved by absolute path from SystemRoot, like
|
|
2154
|
+
* `reg.exe`: never through PATH, which a user can change.
|
|
2155
|
+
*
|
|
2156
|
+
* Windows PowerShell 5.1 ships with Windows, so unlike `pwsh` it can be
|
|
2157
|
+
* relied on; the script is written to its syntax (see
|
|
2158
|
+
* {@link WINDOWS_POWERSHELL_QUERY_SCRIPT}).
|
|
2159
|
+
*/
|
|
2160
|
+
const powershellPath = systemRoot === void 0 || systemRoot === "" ? void 0 : join(systemRoot, WINDOWS_REGISTRY_RELATIVE_POWERSHELL);
|
|
1947
2161
|
const runTool = (root, signal) => new Promise((resolve, reject) => {
|
|
1948
2162
|
if (regPath === void 0) {
|
|
1949
2163
|
reject(new DiscoveryIncompleteError("SystemRoot is not configured"));
|
|
@@ -1961,13 +2175,14 @@ function workBuddyWindowsDiscoveryTools() {
|
|
|
1961
2175
|
], {
|
|
1962
2176
|
maxBuffer: WINDOWS_REGISTRY_OUTPUT_MAX_BYTES,
|
|
1963
2177
|
timeout: WORKBUDDY_DISCOVERY_STEP_TIMEOUT_MS,
|
|
1964
|
-
windowsHide: true
|
|
2178
|
+
windowsHide: true,
|
|
2179
|
+
encoding: "buffer"
|
|
1965
2180
|
}, (error, stdout, stderr) => {
|
|
1966
2181
|
if (settled) return;
|
|
1967
2182
|
settled = true;
|
|
1968
2183
|
if (error !== null && error !== void 0) {
|
|
1969
|
-
if (error.killed !== true && (error.code === 1 || error.code === "1") && windowsRegistryKeyMissing(stderr)) {
|
|
1970
|
-
resolve(
|
|
2184
|
+
if (error.killed !== true && (error.code === 1 || error.code === "1") && windowsRegistryKeyMissing(stderr.toString("utf8"))) {
|
|
2185
|
+
resolve(Buffer.alloc(0));
|
|
1971
2186
|
return;
|
|
1972
2187
|
}
|
|
1973
2188
|
reject(new DiscoveryIncompleteError(`reg.exe could not complete (${error.killed === true ? "timed out" : String(error.code ?? "unavailable")})`));
|
|
@@ -1986,7 +2201,119 @@ function workBuddyWindowsDiscoveryTools() {
|
|
|
1986
2201
|
signal.removeEventListener("abort", abort);
|
|
1987
2202
|
});
|
|
1988
2203
|
});
|
|
1989
|
-
|
|
2204
|
+
/**
|
|
2205
|
+
* Re-read one root through PowerShell's Unicode registry API (#88).
|
|
2206
|
+
*
|
|
2207
|
+
* Returns the same column-shaped document the `reg.exe` path produces, so
|
|
2208
|
+
* the caller's parser and candidate checks apply unchanged. Every failure
|
|
2209
|
+
* that means "we could not tell" is a {@link DiscoveryIncompleteError}, and
|
|
2210
|
+
* a root that simply does not exist yields an empty document — one of the
|
|
2211
|
+
* three roots being absent is normal and must not fail the search.
|
|
2212
|
+
*/
|
|
2213
|
+
const runFallback = (root, signal) => new Promise((resolve, reject) => {
|
|
2214
|
+
if (powershellPath === void 0) {
|
|
2215
|
+
reject(new DiscoveryIncompleteError("SystemRoot is not configured"));
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
if (signal.aborted) {
|
|
2219
|
+
reject(new DiscoveryIncompleteError("powershell.exe was not started: the discovery budget was already spent"));
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
let settled = false;
|
|
2223
|
+
const child = execFile(powershellPath, [
|
|
2224
|
+
"-NoProfile",
|
|
2225
|
+
"-NonInteractive",
|
|
2226
|
+
"-Command",
|
|
2227
|
+
WINDOWS_POWERSHELL_QUERY_SCRIPT,
|
|
2228
|
+
"-Root",
|
|
2229
|
+
root
|
|
2230
|
+
], {
|
|
2231
|
+
maxBuffer: WINDOWS_REGISTRY_OUTPUT_MAX_BYTES,
|
|
2232
|
+
timeout: WORKBUDDY_DISCOVERY_STEP_TIMEOUT_MS,
|
|
2233
|
+
windowsHide: true,
|
|
2234
|
+
encoding: "utf8"
|
|
2235
|
+
}, (error, stdout) => {
|
|
2236
|
+
if (settled) return;
|
|
2237
|
+
settled = true;
|
|
2238
|
+
if (error !== null && error !== void 0) {
|
|
2239
|
+
const code = error.killed === true ? void 0 : Number(error.code);
|
|
2240
|
+
if (code === WINDOWS_POWERSHELL_ROOT_ABSENT) {
|
|
2241
|
+
resolve("");
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
if (code === WINDOWS_POWERSHELL_ROOT_UNREADABLE) {
|
|
2245
|
+
reject(new DiscoveryIncompleteError(`powershell.exe could not read the registry root ${root}`));
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
reject(new DiscoveryIncompleteError(`powershell.exe could not complete (${error.killed === true ? "timed out" : String(error.code ?? "unavailable")})`));
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const records = parseWindowsUninstallJson(stdout);
|
|
2252
|
+
if (records === void 0) {
|
|
2253
|
+
reject(new DiscoveryIncompleteError("powershell.exe output was not the expected registry document"));
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
resolve(records);
|
|
2257
|
+
});
|
|
2258
|
+
const abort = () => {
|
|
2259
|
+
if (settled) return;
|
|
2260
|
+
settled = true;
|
|
2261
|
+
child.kill();
|
|
2262
|
+
reject(new DiscoveryIncompleteError("powershell.exe was abandoned: the discovery budget was spent"));
|
|
2263
|
+
};
|
|
2264
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
2265
|
+
child.on("close", () => {
|
|
2266
|
+
signal.removeEventListener("abort", abort);
|
|
2267
|
+
});
|
|
2268
|
+
});
|
|
2269
|
+
return { queryUninstallRoot: async (root, signal) => {
|
|
2270
|
+
const raw = await runTool(root, signal);
|
|
2271
|
+
if (isAsciiOnly(raw)) return raw.toString("utf8");
|
|
2272
|
+
return await runFallback(root, signal);
|
|
2273
|
+
} };
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Whether every byte is ASCII, the one case where a decode cannot be wrong.
|
|
2277
|
+
*
|
|
2278
|
+
* @param bytes - the child's raw stdout.
|
|
2279
|
+
*/
|
|
2280
|
+
function isAsciiOnly(bytes) {
|
|
2281
|
+
for (const byte of bytes) if (byte >= 128) return false;
|
|
2282
|
+
return true;
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Turn the fallback's JSON into the same column-shaped document
|
|
2286
|
+
* {@link parseWindowsRegistryOutput} already consumes.
|
|
2287
|
+
*
|
|
2288
|
+
* Re-encoding into the `reg query` text form on purpose: the parser and
|
|
2289
|
+
* {@link parseWindowsDisplayIcon} carry the candidate-quoting rules that are
|
|
2290
|
+
* shared with existing tests, and the seam between this tool and the provider
|
|
2291
|
+
* is a string. Records carrying neither value are skipped, and the key name is
|
|
2292
|
+
* synthetic because only the values are read.
|
|
2293
|
+
*
|
|
2294
|
+
* @param output - the child's UTF-8 stdout.
|
|
2295
|
+
* @returns the document, or `undefined` when it was not the expected JSON.
|
|
2296
|
+
*/
|
|
2297
|
+
function parseWindowsUninstallJson(output) {
|
|
2298
|
+
let document;
|
|
2299
|
+
try {
|
|
2300
|
+
document = JSON.parse(output);
|
|
2301
|
+
} catch {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
const rows = Array.isArray(document) ? document : [document];
|
|
2305
|
+
const lines = [];
|
|
2306
|
+
for (const [index, row] of rows.entries()) {
|
|
2307
|
+
if (typeof row !== "object" || row === null) continue;
|
|
2308
|
+
const record = row;
|
|
2309
|
+
const displayName = record["displayName"];
|
|
2310
|
+
const displayIcon = record["displayIcon"];
|
|
2311
|
+
if (typeof displayName !== "string" && typeof displayIcon !== "string") continue;
|
|
2312
|
+
lines.push(`HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{PS-${String(index)}}`);
|
|
2313
|
+
if (typeof displayName === "string") lines.push(` DisplayName REG_SZ ${displayName}`);
|
|
2314
|
+
if (typeof displayIcon === "string") lines.push(` DisplayIcon REG_SZ ${displayIcon}`);
|
|
2315
|
+
}
|
|
2316
|
+
return lines.join("\r\n");
|
|
1990
2317
|
}
|
|
1991
2318
|
/**
|
|
1992
2319
|
* In-memory protector-key resolver: one spawn per key id, single-flight, never
|
|
@@ -3611,7 +3938,7 @@ var WorkBuddyCatalog = class {
|
|
|
3611
3938
|
};
|
|
3612
3939
|
//#endregion
|
|
3613
3940
|
//#region src/version.ts
|
|
3614
|
-
const WORKBUDDY_CONNECT_VERSION = "0.7.
|
|
3941
|
+
const WORKBUDDY_CONNECT_VERSION = "0.7.2";
|
|
3615
3942
|
//#endregion
|
|
3616
3943
|
//#region src/host-heartbeat.ts
|
|
3617
3944
|
/**
|
|
@@ -3782,4 +4109,4 @@ function isHeartbeatProcessAlive(heartbeat) {
|
|
|
3782
4109
|
return startAtMs <= heartbeat.registeredAt;
|
|
3783
4110
|
}
|
|
3784
4111
|
//#endregion
|
|
3785
|
-
export { extractDisplayErrorMessage as A,
|
|
4112
|
+
export { validAppVersion as $, extractDisplayErrorMessage as A, randomSentinel as B, WORKBUDDY_VARIANTS as C, WORKBUDDY_UPDATE_PATH as D, WORKBUDDY_STATUS_PATH as E, prepareCnChatBody as F, readCliVersion as G, FALLBACK_CN_APP_VERSION as H, prepareInternationalChatBody as I, WORKBUDDY_APP_VERSION_FILENAME as J, resolveChatIdentity as K, regionOf as L, normalizeCredits as M, parseModelCatalog as N, WorkBuddyUpstreamClient as O, prepareChatBody as P, resolveAppVersion as Q, PROBE_EFFORT_CANDIDATES as R, CN_VARIANT as S, WORKBUDDY_PROBE_PATH as T, chatUserAgent as U, CN_APP_VERSION_FILENAME as V, fallbackChatIdentity as W, installedAppVersion as X, appUserAgent as Y, readBundleVersion as Z, desktopAuthCandidatesFor as _, readHostHeartbeat as a, atRestKeyProviderFor as b, WORKBUDDY_CONNECT_VERSION as c, WorkBuddyCatalog as d, WORKBUDDY_AUTH_FILENAME as f, defaultDesktopAuthPath as g, defaultDesktopAuthCandidates as h, processStartTimeMs as i, modelWithCurrentPromotion as j, classifyUpstreamError as k, FALLBACK_WORKBUDDY_AI_MODELS as l, WorkBuddyCredentialStore as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, WORKBUDDY_AUTH_FILE_ENV as p, validCliVersion as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, FALLBACK_WORKBUDDY_MODELS as u, parseWorkBuddyAuth as v, variantFor as w, AI_VARIANT as x, workbuddyOwnAuthPath as y, probeModel as z };
|
package/lib/index.d.ts
CHANGED
|
@@ -229,17 +229,22 @@ type ProbeOutcome = {
|
|
|
229
229
|
requests: number;
|
|
230
230
|
reason: string;
|
|
231
231
|
};
|
|
232
|
+
/** Which endpoint's rejection vocabulary a probe interprets. */
|
|
233
|
+
type ProbeRegion = 'cn' | 'global';
|
|
232
234
|
/**
|
|
233
235
|
* Probe one model.
|
|
234
236
|
*
|
|
235
237
|
* `options.candidates` exists so tests can shorten the sweep; production always
|
|
236
|
-
* uses {@link PROBE_EFFORT_CANDIDATES}.
|
|
238
|
+
* uses {@link PROBE_EFFORT_CANDIDATES}. `options.region` selects which
|
|
239
|
+
* endpoint's rejection vocabulary is read; it defaults to `cn`, which is also
|
|
240
|
+
* the production default for the China app.
|
|
237
241
|
*/
|
|
238
242
|
declare function probeModel(options: {
|
|
239
243
|
send: ProbeSender;
|
|
240
244
|
sentinel?: SentinelFactory;
|
|
241
245
|
candidates?: readonly WorkBuddyEffort[];
|
|
242
246
|
timeoutMs?: number;
|
|
247
|
+
region?: ProbeRegion;
|
|
243
248
|
}): Promise<ProbeOutcome>;
|
|
244
249
|
//#endregion
|
|
245
250
|
//#region src/upstream.d.ts
|
|
@@ -325,6 +330,49 @@ interface WorkBuddyCreditAccount {
|
|
|
325
330
|
remain: number;
|
|
326
331
|
size: number;
|
|
327
332
|
unlimited?: true;
|
|
333
|
+
/**
|
|
334
|
+
* When the package's own cycle ends, in epoch milliseconds.
|
|
335
|
+
*
|
|
336
|
+
* The upstream has always sent this (`CycleEndTime`, a zone-less
|
|
337
|
+
* `YYYY-MM-DD HH:mm:ss` string) and the query itself already filters on
|
|
338
|
+
* `PackageEndTimeRange`, but the field used to be dropped here — so a card
|
|
339
|
+
* could show "500 credit" for a package expiring in an hour with nothing to
|
|
340
|
+
* distinguish it from one expiring next month. Absent when the upstream omits
|
|
341
|
+
* or malforms it, which must never read as "no expiry".
|
|
342
|
+
*
|
|
343
|
+
* The zone the upstream means is **not established**: reading the string as
|
|
344
|
+
* the running machine's local time follows the official desktop client (see
|
|
345
|
+
* {@link parseCycleEndTime}), and is not a claim about the account's zone.
|
|
346
|
+
*/
|
|
347
|
+
endTimeMs?: number;
|
|
348
|
+
}
|
|
349
|
+
/** Credit whose package expires within a horizon the user should act on. */
|
|
350
|
+
interface WorkBuddyExpiringCredits {
|
|
351
|
+
/** Sum of {@link WorkBuddyCreditAccount.remain} over the window. */
|
|
352
|
+
total: number;
|
|
353
|
+
/** How many packages contribute to {@link total}. */
|
|
354
|
+
packages: number;
|
|
355
|
+
/** The soonest `endTimeMs` among them. */
|
|
356
|
+
earliestEndTimeMs?: number;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Credit at risk, bucketed by how soon it lapses.
|
|
360
|
+
*
|
|
361
|
+
* Thresholds are calendar-ish (24h / 3d / 7d) rather than "this month": a package
|
|
362
|
+
* expiring at 08:00 tomorrow is not helped by a monthly average, and the upstream
|
|
363
|
+
* genuinely staggers expiries per package.
|
|
364
|
+
*
|
|
365
|
+
* Only *future* horizons are reported. An already-lapsed package is deliberately
|
|
366
|
+
* **not** summarised here: the personal billing query this feeds filters
|
|
367
|
+
* `Status: [0, 3]` (`valid` + `usedUp`) from the current time forward, while the
|
|
368
|
+
* upstream marks a lapsed package `Status: 2` (`expired`) — a status this query
|
|
369
|
+
* never returns. Historical expiry would need its own query, which is out of
|
|
370
|
+
* scope.
|
|
371
|
+
*/
|
|
372
|
+
interface WorkBuddyCreditExpiry {
|
|
373
|
+
within24h: WorkBuddyExpiringCredits;
|
|
374
|
+
within3d: WorkBuddyExpiringCredits;
|
|
375
|
+
within7d: WorkBuddyExpiringCredits;
|
|
328
376
|
}
|
|
329
377
|
/** Aggregated credit answer for one credential. */
|
|
330
378
|
interface WorkBuddyCredits {
|
|
@@ -342,6 +390,13 @@ interface WorkBuddyCredits {
|
|
|
342
390
|
*/
|
|
343
391
|
unlimited?: true;
|
|
344
392
|
cycleResetTime?: string;
|
|
393
|
+
/**
|
|
394
|
+
* Credit lapsing soon, so a card can warn instead of only reporting a total.
|
|
395
|
+
*
|
|
396
|
+
* Absent when no account carried a usable end time — the plugin must not
|
|
397
|
+
* claim "nothing expires soon" on the strength of data it never received.
|
|
398
|
+
*/
|
|
399
|
+
expiry?: WorkBuddyCreditExpiry;
|
|
345
400
|
}
|
|
346
401
|
/** Token refresh answer; fields the upstream omits stay absent. */
|
|
347
402
|
interface WorkBuddyRefreshOutcome {
|
|
@@ -604,6 +659,22 @@ declare function modelWithCurrentPromotion(model: WorkBuddyUpstreamModel, now?:
|
|
|
604
659
|
* JSON object is returned unchanged, exactly as {@link prepareChatBody} does,
|
|
605
660
|
* so this is safe to run over an already-prepared-or-not body.
|
|
606
661
|
*/
|
|
662
|
+
/**
|
|
663
|
+
* Prepare a body for the **CN** endpoint.
|
|
664
|
+
*
|
|
665
|
+
* Until #87 the CN wire was the caller's body verbatim. The only difference
|
|
666
|
+
* now is the shared {@link dropUnsupportedEffort} strip, whose region scope was
|
|
667
|
+
* lifted once measurements showed this endpoint rejects `off` too.
|
|
668
|
+
*
|
|
669
|
+
* Deliberately **not** routed through {@link prepareChatBody}: the shim already
|
|
670
|
+
* applies it before handing the body to {@link WorkBuddyUpstreamClient.chatStream},
|
|
671
|
+
* and re-running it would be a second `stream`/`developer`/`tool_choice`
|
|
672
|
+
* normalisation of an already-normalised body. Only the strip belongs here.
|
|
673
|
+
*
|
|
674
|
+
* A body that is not a JSON object is returned unchanged, so this is safe to
|
|
675
|
+
* run over an already-prepared-or-not body.
|
|
676
|
+
*/
|
|
677
|
+
declare function prepareCnChatBody(source: string): string;
|
|
607
678
|
declare function prepareInternationalChatBody(source: string): string;
|
|
608
679
|
//#endregion
|
|
609
680
|
//#region src/variants.d.ts
|
|
@@ -1271,6 +1342,12 @@ declare const WORKBUDDY_STREAM_IDLE_TIMEOUT_MS = 300000;
|
|
|
1271
1342
|
interface WorkBuddyAdapterOptions {
|
|
1272
1343
|
providerId?: string;
|
|
1273
1344
|
displayName?: string;
|
|
1345
|
+
/**
|
|
1346
|
+
* Which endpoint this adapter serves, from the variant descriptor. Decides
|
|
1347
|
+
* only whether the CN `onlyReasoning` veto hides `off` (#87); defaults to
|
|
1348
|
+
* `'cn'`, the same legacy default `providerId` carries.
|
|
1349
|
+
*/
|
|
1350
|
+
region?: WorkBuddyRegion;
|
|
1274
1351
|
shim: WorkBuddyShim;
|
|
1275
1352
|
store: WorkBuddyCredentialStore;
|
|
1276
1353
|
catalog: WorkBuddyCatalog;
|
|
@@ -1463,6 +1540,12 @@ interface WorkBuddyProbeServiceOptions {
|
|
|
1463
1540
|
*/
|
|
1464
1541
|
account: () => string | undefined;
|
|
1465
1542
|
sentinel?: SentinelFactory;
|
|
1543
|
+
/**
|
|
1544
|
+
* Which endpoint's rejection vocabulary sweeps read. The two apps talk to
|
|
1545
|
+
* different upstreams that answer a bad effort with different codes, so each
|
|
1546
|
+
* runtime passes its own region instead of sharing one widening set.
|
|
1547
|
+
*/
|
|
1548
|
+
region: ProbeRegion;
|
|
1466
1549
|
/** Injectable for tests; defaults to the live upstream sender. */
|
|
1467
1550
|
send?: (modelId: string) => ProbeSender;
|
|
1468
1551
|
}
|
|
@@ -1634,4 +1717,4 @@ declare function visibilityAccountOf(credential: Pick<WorkBuddyCredential, 'uid'
|
|
|
1634
1717
|
*/
|
|
1635
1718
|
declare function apply(ctx: Context, config: Config): void;
|
|
1636
1719
|
//#endregion
|
|
1637
|
-
export { AI_VARIANT, type AppVersionInfo, CN_APP_VERSION_FILENAME, CN_VARIANT, type ChatIdentity, Config, FALLBACK_CN_APP_VERSION, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, type ProbeAttempt, type ProbeOutcome, type ProbeSender, type ResolveChatIdentityOptions, type UpstreamErrorKind, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, WORKBUDDY_VISIBILITY_FILENAME, type WorkBuddyAdapter, type WorkBuddyAppVersionSource, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyCatalogFetch, WorkBuddyCatalogStore, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyEffort, type WorkBuddyHostHeartbeat, type WorkBuddyModelBilling, type WorkBuddyModelInfo, type WorkBuddyModelReasoning, type WorkBuddyProbeRecord, WorkBuddyProbeService, type WorkBuddyProbeStatus, WorkBuddyProbeStore, type WorkBuddyProbeValidation, type WorkBuddyPromotion, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyVariant, WorkBuddyVisibilityStore, appUserAgent, apply, chatUserAgent, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fallbackChatIdentity, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readCliVersion, readHostHeartbeat, regionOf, resolveAppVersion, resolveChatIdentity, validAppVersion, validCliVersion, variantFor, visibilityAccountOf, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath, workbuddyVisibilityPath };
|
|
1720
|
+
export { AI_VARIANT, type AppVersionInfo, CN_APP_VERSION_FILENAME, CN_VARIANT, type ChatIdentity, Config, FALLBACK_CN_APP_VERSION, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, type ProbeAttempt, type ProbeOutcome, type ProbeSender, type ResolveChatIdentityOptions, type UpstreamErrorKind, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, WORKBUDDY_VISIBILITY_FILENAME, type WorkBuddyAdapter, type WorkBuddyAppVersionSource, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyCatalogFetch, WorkBuddyCatalogStore, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyEffort, type WorkBuddyHostHeartbeat, type WorkBuddyModelBilling, type WorkBuddyModelInfo, type WorkBuddyModelReasoning, type WorkBuddyProbeRecord, WorkBuddyProbeService, type WorkBuddyProbeStatus, WorkBuddyProbeStore, type WorkBuddyProbeValidation, type WorkBuddyPromotion, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyVariant, WorkBuddyVisibilityStore, appUserAgent, apply, chatUserAgent, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fallbackChatIdentity, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareCnChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readCliVersion, readHostHeartbeat, regionOf, resolveAppVersion, resolveChatIdentity, validAppVersion, validCliVersion, variantFor, visibilityAccountOf, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath, workbuddyVisibilityPath };
|