dsh-usage-state 0.3.2 → 0.4.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/README.en.md +7 -3
- package/README.md +7 -3
- package/cordis.patch.yml +12 -1
- package/lib/client.js +126 -44
- package/lib/index.js +72 -27
- package/package.json +15 -8
package/README.en.md
CHANGED
|
@@ -22,7 +22,8 @@ hover any segment: Source DeepSeek · Mode API balance · Granted 0 ·
|
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
|
-
**Requirements**: DSH `0.
|
|
25
|
+
**Requirements**: DSH `0.2.0-rc.2` or a later 0.2 release, Node ≥ 20, installed into the `web` profile. The package **ships the prebuilt `lib/`**, so installation has no build step.
|
|
26
|
+
**On the 0.1 line install `0.3.2` instead**: DSH 0.2 replaced the whole settings API (`settingsScope` / `settings.register` → `configForms` / the plugin's own `Config`), and the two share no surface, so `0.4.0` and up support the 0.2 line only (see revision 17 of [`docs/design-consensus.md`](docs/design-consensus.md)).
|
|
26
27
|
|
|
27
28
|
```bash
|
|
28
29
|
# 1) install (npm package)
|
|
@@ -98,9 +99,12 @@ Expanding **Advanced** lets you override the source and endpoint, name a credent
|
|
|
98
99
|
|
|
99
100
|
## Compatibility
|
|
100
101
|
|
|
101
|
-
-
|
|
102
|
+
- **0.2 line only**: `engines.dsh` = `>=0.2.0-rc.2 <0.3.0-0` (this is what dsh-market's badge and install gate read). On an earlier host, install `0.3.2`.
|
|
103
|
+
- `peerDependencies` carry `@deepseek-ai/dsh-settings` (`^0.2.0-rc.2`) and `@deepseek-ai/schemastery` (`^3.18.2`). The first one is deliberate: the runtime install gate only reads `@deepseek-ai/dsh*` peers, so declaring it makes a 0.1.x host **refuse the install** instead of accepting it and then failing to boot. Both are platform-provided; pnpm may print a `missing peer` warning for them, which is expected.
|
|
104
|
+
- Version `0.4.1` fixes `0.4.0`'s missing readings: the browser half's RPC contribution lacked the `create()` factory 0.2 requires, so `remote.usageState` never mounted — the UI showed **no readings at all** and mislabelled the cause as `Mode not supported`. The same release stops swallowing a rejected mount, says "reading" while the catalog is absent, and adds **real 0.2 registry contract tests** (revision 18). **Use `0.4.1`, not `0.4.0`.**
|
|
105
|
+
- Version `0.4.0` migrates to the 0.2 settings model (`Config` + `configForms`, with cross-entry config read through `configEditor`). Verification and results: [`docs/implementation.md`](docs/implementation.md) §10; rationale: [`docs/design-consensus.md`](docs/design-consensus.md) revision 17.
|
|
106
|
+
- Version `0.3.2` (last of the 0.1 line) only widened `engines.dsh`; **its claimed 0.2 compatibility was wrong** — on `0.2.0-rc.2` it fails the entire web boot because `settingsScope` does not exist there.
|
|
102
107
|
- Published to npm as [`dsh-usage-state`](https://www.npmjs.com/package/dsh-usage-state); GitHub installs work too.
|
|
103
|
-
- Version `0.3.2`: 0.2-line compatibility review (no code change — only `engines.dsh` was widened); DeepSeek, z.ai and OpenCode Zen Go are verified against live accounts; see the limitations below.
|
|
104
108
|
|
|
105
109
|
## Limitations
|
|
106
110
|
|
package/README.md
CHANGED
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
## 安装
|
|
26
26
|
|
|
27
|
-
**前置条件**:DSH `0.
|
|
27
|
+
**前置条件**:DSH `0.2.0-rc.2` 或更新的 0.2 线、Node ≥ 20,装进 `web` profile。包内**自带预构建的 `lib/`**,安装时没有构建步骤。
|
|
28
|
+
**用 0.1 线请装 `0.3.2`**:DSH 0.2 换掉了整套设置 API(`settingsScope` / `settings.register` → `configForms` / 插件自带 `Config`),两套 API 没有重叠,所以 `0.4.0` 起只支持 0.2 线(原因见 [`docs/design-consensus.md`](docs/design-consensus.md) 修订 17)。
|
|
28
29
|
|
|
29
30
|
```bash
|
|
30
31
|
# 1) 安装(npm 包)
|
|
@@ -122,9 +123,12 @@ dsh plugin --profile web add /path/to/dsh-usage-state
|
|
|
122
123
|
|
|
123
124
|
## 兼容性
|
|
124
125
|
|
|
125
|
-
-
|
|
126
|
+
- **只支持 DSH 0.2 线**:`engines.dsh` = `>=0.2.0-rc.2 <0.3.0-0`(市场读它做徽标与"可安装"判定)。0.2 之前的宿主请装 `0.3.2`。
|
|
127
|
+
- `peerDependencies` 里有 `@deepseek-ai/dsh-settings`(`^0.2.0-rc.2`)与 `@deepseek-ai/schemastery`(`^3.18.2`):前者是**故意的**——平台的运行时安装闸门只读 `@deepseek-ai/dsh*` 的 peer,声明它可以让 0.1.x 宿主在**安装时**就拒绝,而不是装上之后把启动搞崩;两者都由平台提供,pnpm 可能为此打一行 `missing peer` 警告,属预期。
|
|
128
|
+
- 版本 `0.4.1`:修 `0.4.0` 的读数缺失——客户端 RPC contribution 的参数 codec 少了 0.2 要求的 `create()`,`remote.usageState` 因此从未挂载,界面表现为**没有任何读数**并误报 `Mode not supported`。同一轮还让挂载失败不再被静默吞掉、catalog 未到时改说「读取中」,并把**真实 0.2 registry 契约测试**写进单测(修订 18)。**请用 `0.4.1`,不要用 `0.4.0`。**
|
|
129
|
+
- 版本 `0.4.0`:迁到 0.2 原生设置模型(`Config` + `configForms`,宿主侧 `configEditor` 读跨条目配置)。验证方式与结论见 [`docs/implementation.md`](docs/implementation.md) §10、决策见 [`docs/design-consensus.md`](docs/design-consensus.md) 修订 17。
|
|
130
|
+
- 版本 `0.3.2`(0.1 线末版):仅放宽 `engines.dsh`;**它声称的 0.2 兼容是错的**(0.3.2 在 0.2.0-rc.2 上会因 `settingsScope` 不存在而让整个 web 启动失败)。
|
|
126
131
|
- 同名发布在 npm([`dsh-usage-state`](https://www.npmjs.com/package/dsh-usage-state)),也可从 GitHub 直接安装。
|
|
127
|
-
- 版本 `0.3.2`:DSH 0.2 发布线的兼容复核(无代码改动,只放宽 `engines.dsh`);DeepSeek、z.ai 与 OpenCode Zen Go 已在真机验证,其余见下。
|
|
128
132
|
|
|
129
133
|
## 已知限制
|
|
130
134
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
# Mount layer contributed by this plugin.
|
|
2
2
|
#
|
|
3
3
|
# `dsh plugin add` appends this package to the profile's `dsh.profile.bundles`,
|
|
4
|
-
# which applies this patch on boot
|
|
4
|
+
# which applies this patch on boot.
|
|
5
|
+
#
|
|
6
|
+
# The row id **is the settings namespace** on DSH 0.2 (`configForms.get(id)` on the
|
|
7
|
+
# client, `entry.options.id` on the host), so it has to keep matching
|
|
8
|
+
# `USAGE_STATE_NS` in `src/host/settings.ts`.
|
|
9
|
+
#
|
|
10
|
+
# `config: {}` gives every install a section to resolve: without it the entry has
|
|
11
|
+
# no config at all, the settings projection answers `undefined`, and the status
|
|
12
|
+
# line stays silent until the user saves something. A user's own
|
|
13
|
+
# `- id: usage-state, config: { … }` row still replaces this whole value, because
|
|
14
|
+
# the profile patch layer applies after every bundle layer.
|
|
5
15
|
- insert:
|
|
6
16
|
- id: usage-state
|
|
7
17
|
name: dsh-usage-state
|
|
18
|
+
config: {}
|
package/lib/client.js
CHANGED
|
@@ -7,6 +7,47 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/client/contribution.ts
|
|
11
|
+
/** Absent or boolean — the one parameter this plugin's RPC takes. */
|
|
12
|
+
const forceSchema = { parse: (value) => value === void 0 ? void 0 : value === true };
|
|
13
|
+
/**
|
|
14
|
+
* A strict codec built without zod: `schema` is what the wire decodes through, and
|
|
15
|
+
* `create()` is the factory the platform's registry calls to obtain it.
|
|
16
|
+
*/
|
|
17
|
+
const booleanOrUndefined = {
|
|
18
|
+
mode: "strict",
|
|
19
|
+
typeSymbol: "dsh-usage-state#Force",
|
|
20
|
+
create: () => forceSchema
|
|
21
|
+
};
|
|
22
|
+
const srcJson = { mode: "src-json" };
|
|
23
|
+
/** Must mirror `src/host/typert.ts`: the wire endpoint is `<namespace>/<method>`. */
|
|
24
|
+
const CONTRIBUTION = {
|
|
25
|
+
package: "dsh-usage-state",
|
|
26
|
+
descriptors: [{
|
|
27
|
+
id: "dsh-usage-state#usageState/getState",
|
|
28
|
+
service: "usageState",
|
|
29
|
+
namespace: "usageState",
|
|
30
|
+
method: "getState",
|
|
31
|
+
invocation: { kind: "direct" },
|
|
32
|
+
parameters: [{
|
|
33
|
+
name: "force",
|
|
34
|
+
wire: "force",
|
|
35
|
+
source: "json",
|
|
36
|
+
acceptsUndefined: true,
|
|
37
|
+
codec: booleanOrUndefined
|
|
38
|
+
}],
|
|
39
|
+
result: srcJson
|
|
40
|
+
}, {
|
|
41
|
+
id: "dsh-usage-state#usageState/describeCredentials",
|
|
42
|
+
service: "usageState",
|
|
43
|
+
namespace: "usageState",
|
|
44
|
+
method: "describeCredentials",
|
|
45
|
+
invocation: { kind: "direct" },
|
|
46
|
+
parameters: [],
|
|
47
|
+
result: srcJson
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
10
51
|
//#region src/shared/config.ts
|
|
11
52
|
const DEFAULT_CONFIG = {
|
|
12
53
|
models: [],
|
|
@@ -1067,6 +1108,10 @@ window.__ModuleLoader__.load({
|
|
|
1067
1108
|
function describeStatus(input) {
|
|
1068
1109
|
const { status, snapshot, display, now } = input;
|
|
1069
1110
|
if (status.kind === "hidden") return [];
|
|
1111
|
+
if (status.kind === "loading") return [{
|
|
1112
|
+
kind: "state",
|
|
1113
|
+
state: "loading"
|
|
1114
|
+
}];
|
|
1070
1115
|
if (status.kind === "unconfigured") return [{
|
|
1071
1116
|
kind: "state",
|
|
1072
1117
|
state: "unconfigured"
|
|
@@ -1287,12 +1332,15 @@ window.__ModuleLoader__.load({
|
|
|
1287
1332
|
}
|
|
1288
1333
|
}
|
|
1289
1334
|
/** Map a provider resolution onto what the line can say about it. */
|
|
1290
|
-
function statusOf(resolution) {
|
|
1335
|
+
function statusOf(resolution, catalogEmpty) {
|
|
1291
1336
|
switch (resolution.reason) {
|
|
1292
1337
|
case "hidden": return { kind: "hidden" };
|
|
1293
1338
|
case "needs-endpoint": return { kind: "needs-endpoint" };
|
|
1294
1339
|
case "unknown-source":
|
|
1295
|
-
case "unsupported":
|
|
1340
|
+
case "unsupported":
|
|
1341
|
+
if (resolution.sourceId === null) return { kind: "unconfigured" };
|
|
1342
|
+
if (catalogEmpty) return { kind: "loading" };
|
|
1343
|
+
return { kind: "unsupported" };
|
|
1296
1344
|
case "auto":
|
|
1297
1345
|
case "configured":
|
|
1298
1346
|
if (resolution.key === void 0 || resolution.sourceId === null || resolution.mode === null) return { kind: "unconfigured" };
|
|
@@ -1323,11 +1371,15 @@ window.__ModuleLoader__.load({
|
|
|
1323
1371
|
provider: current.provider,
|
|
1324
1372
|
config,
|
|
1325
1373
|
catalog: state.catalog
|
|
1326
|
-
}));
|
|
1374
|
+
}), state.catalog.length === 0);
|
|
1327
1375
|
const snapshot = status.kind === "ready" ? state.snapshots[status.key] : void 0;
|
|
1328
1376
|
const sourceLabel = status.kind === "ready" ? state.catalog.find((entry) => entry.id === status.sourceId)?.displayName ?? status.sourceId : "";
|
|
1329
1377
|
const fullParts = statusParts({
|
|
1330
|
-
segments:
|
|
1378
|
+
segments: state.catalog.length === 0 && state.status === "error" && state.error !== void 0 ? [{
|
|
1379
|
+
kind: "state",
|
|
1380
|
+
state: "error",
|
|
1381
|
+
errorDetail: state.error
|
|
1382
|
+
}] : describeStatus({
|
|
1331
1383
|
sourceLabel,
|
|
1332
1384
|
status,
|
|
1333
1385
|
snapshot,
|
|
@@ -1524,6 +1576,54 @@ window.__ModuleLoader__.load({
|
|
|
1524
1576
|
}
|
|
1525
1577
|
}
|
|
1526
1578
|
//#endregion
|
|
1579
|
+
//#region src/client/settings-form.ts
|
|
1580
|
+
/**
|
|
1581
|
+
* Present one `configForms` entry as the settings scope the components consume.
|
|
1582
|
+
*
|
|
1583
|
+
* Two things the platform form does not do for us:
|
|
1584
|
+
*
|
|
1585
|
+
* - **Decoding.** The host hands the stored section through unchanged, because this
|
|
1586
|
+
* plugin's schema declares no fields on purpose (see `host/settings.ts`). So
|
|
1587
|
+
* `normalizeConfig` runs here, keeping the single lenient decoder the plugin has
|
|
1588
|
+
* always had. A namespace the platform has not answered yet keeps `value`
|
|
1589
|
+
* undefined, which is what makes the status line stay silent instead of drawing
|
|
1590
|
+
* defaults the host never accepted.
|
|
1591
|
+
* - **Snapshot identity.** `useSettingsValue` stores whatever `getSnapshot()`
|
|
1592
|
+
* returns, so a fresh object per call would re-render forever. The decoded
|
|
1593
|
+
* snapshot is cached against the platform snapshot it came from.
|
|
1594
|
+
*/
|
|
1595
|
+
function usageStateSettings(form) {
|
|
1596
|
+
let source;
|
|
1597
|
+
let decoded;
|
|
1598
|
+
const snapshot = () => {
|
|
1599
|
+
const next = form.getSnapshot();
|
|
1600
|
+
if (decoded === void 0 || next !== source) {
|
|
1601
|
+
source = next;
|
|
1602
|
+
decoded = {
|
|
1603
|
+
status: next.status,
|
|
1604
|
+
value: next.value === void 0 ? void 0 : normalizeConfig(next.value),
|
|
1605
|
+
revision: next.revision,
|
|
1606
|
+
writable: next.writable,
|
|
1607
|
+
mode: next.mode
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
return decoded;
|
|
1611
|
+
};
|
|
1612
|
+
return {
|
|
1613
|
+
getSnapshot: snapshot,
|
|
1614
|
+
subscribe: (listener) => form.subscribe(listener),
|
|
1615
|
+
set: async (field, value) => {
|
|
1616
|
+
await form.set(field, value);
|
|
1617
|
+
},
|
|
1618
|
+
unset: async (field) => {
|
|
1619
|
+
await form.unset(field);
|
|
1620
|
+
},
|
|
1621
|
+
mutate: async (ops, expectedRevision) => {
|
|
1622
|
+
await form.mutate(ops, expectedRevision);
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
//#endregion
|
|
1527
1627
|
//#region src/client/slots.ts
|
|
1528
1628
|
const STATUS_LINE_SLOTS = [{
|
|
1529
1629
|
name: "conversation.composer.dock",
|
|
@@ -1674,63 +1774,41 @@ window.__ModuleLoader__.load({
|
|
|
1674
1774
|
this.publish({ credentialsError: messageOf(error) });
|
|
1675
1775
|
}
|
|
1676
1776
|
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Record that the RPC contribution never mounted.
|
|
1779
|
+
*
|
|
1780
|
+
* Without this the only symptom is an empty catalog, which every surface reads
|
|
1781
|
+
* as "nothing configured yet" — the real cause has to be nameable, because it is
|
|
1782
|
+
* a plugin/host contract failure, not something the user can configure away.
|
|
1783
|
+
*/
|
|
1784
|
+
failRemote(message) {
|
|
1785
|
+
this.publish({
|
|
1786
|
+
status: "error",
|
|
1787
|
+
error: message,
|
|
1788
|
+
catalog: [],
|
|
1789
|
+
snapshots: {}
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1677
1792
|
};
|
|
1678
1793
|
//#endregion
|
|
1679
1794
|
//#region src/client/index.tsx
|
|
1680
1795
|
const inject = [
|
|
1681
1796
|
"slots",
|
|
1682
1797
|
"locale",
|
|
1683
|
-
"
|
|
1798
|
+
"configForms",
|
|
1684
1799
|
"remote",
|
|
1685
1800
|
"remote.session",
|
|
1686
1801
|
"remote.credentials"
|
|
1687
1802
|
];
|
|
1688
1803
|
const USAGE_STATE_NS = "usage-state";
|
|
1689
1804
|
const POLL_INTERVAL_MS = 3e4;
|
|
1690
|
-
/** Hand-rolled codecs: the browser bundle must not carry zod. */
|
|
1691
|
-
const booleanOrUndefined = {
|
|
1692
|
-
mode: "strict",
|
|
1693
|
-
typeSymbol: "dsh-usage-state#Force",
|
|
1694
|
-
schema: { parse: (value) => value === void 0 ? void 0 : value === true }
|
|
1695
|
-
};
|
|
1696
|
-
const srcJson = { mode: "src-json" };
|
|
1697
|
-
/** Must mirror `src/host/typert.ts`: the wire endpoint is `<namespace>/<method>`. */
|
|
1698
|
-
const CONTRIBUTION = {
|
|
1699
|
-
package: "dsh-usage-state",
|
|
1700
|
-
descriptors: [{
|
|
1701
|
-
id: "dsh-usage-state#usageState/getState",
|
|
1702
|
-
service: "usageState",
|
|
1703
|
-
namespace: "usageState",
|
|
1704
|
-
method: "getState",
|
|
1705
|
-
invocation: { kind: "direct" },
|
|
1706
|
-
parameters: [{
|
|
1707
|
-
name: "force",
|
|
1708
|
-
wire: "force",
|
|
1709
|
-
source: "json",
|
|
1710
|
-
acceptsUndefined: true,
|
|
1711
|
-
codec: booleanOrUndefined
|
|
1712
|
-
}],
|
|
1713
|
-
result: srcJson
|
|
1714
|
-
}, {
|
|
1715
|
-
id: "dsh-usage-state#usageState/describeCredentials",
|
|
1716
|
-
service: "usageState",
|
|
1717
|
-
namespace: "usageState",
|
|
1718
|
-
method: "describeCredentials",
|
|
1719
|
-
invocation: { kind: "direct" },
|
|
1720
|
-
parameters: [],
|
|
1721
|
-
result: srcJson
|
|
1722
|
-
}]
|
|
1723
|
-
};
|
|
1724
1805
|
function apply(ctx) {
|
|
1725
1806
|
ctx.effect(() => ctx.locale.register(LOCALE_NS, {
|
|
1726
1807
|
zh,
|
|
1727
1808
|
en
|
|
1728
1809
|
}), "dsh-usage-state: dictionaries");
|
|
1729
1810
|
const t = ctx.locale.bind(LOCALE_NS);
|
|
1730
|
-
const settings = ctx.
|
|
1731
|
-
namespace: USAGE_STATE_NS,
|
|
1732
|
-
decode: (section) => normalizeConfig(section)
|
|
1733
|
-
});
|
|
1811
|
+
const settings = usageStateSettings(ctx.configForms.get(USAGE_STATE_NS));
|
|
1734
1812
|
const usageStateRemote = () => remoteService(ctx, "remote.usageState");
|
|
1735
1813
|
const store = new UsageStateClientStore({
|
|
1736
1814
|
getState: async (force) => {
|
|
@@ -1770,7 +1848,11 @@ window.__ModuleLoader__.load({
|
|
|
1770
1848
|
store.refresh(false);
|
|
1771
1849
|
store.refreshModels();
|
|
1772
1850
|
store.refreshCredentials();
|
|
1773
|
-
}, () =>
|
|
1851
|
+
}, (error) => {
|
|
1852
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1853
|
+
console.error("dsh-usage-state: remote contribution did not mount", error);
|
|
1854
|
+
store.failRemote(message);
|
|
1855
|
+
});
|
|
1774
1856
|
return () => {
|
|
1775
1857
|
cancelled = true;
|
|
1776
1858
|
dispose?.();
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
4
5
|
//#region src/shared/config.ts
|
|
5
6
|
const DEFAULT_CONFIG = {
|
|
6
7
|
models: [],
|
|
@@ -1410,29 +1411,42 @@ var UsageStateService = class {
|
|
|
1410
1411
|
};
|
|
1411
1412
|
//#endregion
|
|
1412
1413
|
//#region src/host/settings.ts
|
|
1413
|
-
/** Settings namespace owned by this plugin; must match `/^[a-z][a-z0-9-]*$/`. */
|
|
1414
|
-
const USAGE_STATE_NS = "usage-state";
|
|
1415
|
-
const usageStateSchema = Object.assign((section) => normalizeConfig(section), { toJSON: () => ({
|
|
1416
|
-
uid: 1,
|
|
1417
|
-
refs: { 1: {
|
|
1418
|
-
type: "object",
|
|
1419
|
-
meta: {},
|
|
1420
|
-
dict: {}
|
|
1421
|
-
} }
|
|
1422
|
-
}) });
|
|
1423
1414
|
/**
|
|
1424
|
-
*
|
|
1415
|
+
* The plugin's `Config`, which the 0.2 loader resolves from this module export
|
|
1416
|
+
* (`entry.fiber.runtime.Config`), validates with `Config['~standard']`, and hands
|
|
1417
|
+
* to `apply` as its second argument.
|
|
1425
1418
|
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1419
|
+
* Two deliberate choices, both about staying compatible with documents this
|
|
1420
|
+
* plugin does not fully control:
|
|
1421
|
+
*
|
|
1422
|
+
* - **A volatile root.** The loader hands a volatile schema's value over as a live
|
|
1423
|
+
* reference, commits edits into it in place, and announces them as
|
|
1424
|
+
* `loader/volatile-update`. A non-volatile schema would instead find the config
|
|
1425
|
+
* "changed" on every settings write and remount the plugin.
|
|
1426
|
+
* - **`any`, not a field-by-field object.** The settings service projects form
|
|
1427
|
+
* values *through* the schema, so a declared object schema silently drops every
|
|
1428
|
+
* field it does not declare — including keys a hand-edited document carries, and
|
|
1429
|
+
* including keys a future version adds. Validation stays where it has always
|
|
1430
|
+
* been: `normalizeConfig`, which is deliberately lenient, so a dirty document
|
|
1431
|
+
* still loads instead of failing the entry.
|
|
1432
|
+
*/
|
|
1433
|
+
const Config = z.any().volatile();
|
|
1434
|
+
/**
|
|
1435
|
+
* Keep a live snapshot of the plugin's own config.
|
|
1436
|
+
*
|
|
1437
|
+
* `reference` is `apply`'s second argument. On a host that does not model volatile
|
|
1438
|
+
* config (or when the entry carries no schema) the loader passes nothing, and the
|
|
1439
|
+
* plugin keeps its own defaults — the same graceful degradation the 0.1 path got
|
|
1440
|
+
* from `ctx.inject(['settings'], …)`, which 0.2 no longer offers.
|
|
1430
1441
|
*/
|
|
1431
|
-
function installUsageStateSettings(ctx, onConfig) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
onConfig(normalizeConfig(
|
|
1435
|
-
|
|
1442
|
+
function installUsageStateSettings(ctx, reference, onConfig) {
|
|
1443
|
+
if (reference === void 0) return;
|
|
1444
|
+
const publish = () => {
|
|
1445
|
+
onConfig(normalizeConfig(reference.get()));
|
|
1446
|
+
};
|
|
1447
|
+
publish();
|
|
1448
|
+
ctx.on("loader/volatile-update", () => {
|
|
1449
|
+
publish();
|
|
1436
1450
|
});
|
|
1437
1451
|
}
|
|
1438
1452
|
//#endregion
|
|
@@ -1598,11 +1612,38 @@ function resolveTargets(config, options = {}) {
|
|
|
1598
1612
|
const name = "usage-state";
|
|
1599
1613
|
/** `timer` is what provides `ctx.timeout` / `ctx.interval`. */
|
|
1600
1614
|
const inject = ["timer"];
|
|
1615
|
+
/** The shared volatile-reference protocol (`cosmokit.volatile.write`). */
|
|
1616
|
+
const VOLATILE_WRITE = Symbol.for("cosmokit.volatile.write");
|
|
1617
|
+
/** Whether a loader config value is a live volatile reference rather than data. */
|
|
1618
|
+
function isVolatileRef(value) {
|
|
1619
|
+
return typeof value === "object" && value !== null && VOLATILE_WRITE in value && typeof value.get === "function";
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Detach a loader config: volatile fields are live references, and a snapshot is
|
|
1623
|
+
* only useful once they are read out. Mirrors the platform's own `plainConfig`,
|
|
1624
|
+
* including its refusal to guard against cycles (config cannot contain one).
|
|
1625
|
+
*/
|
|
1626
|
+
function plainConfig(value) {
|
|
1627
|
+
if (isVolatileRef(value)) return plainConfig(value.get());
|
|
1628
|
+
if (Array.isArray(value)) return value.map((item) => plainConfig(item));
|
|
1629
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, plainConfig(item)]));
|
|
1630
|
+
return value;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Another plugin's resolved config, by profile entry id.
|
|
1634
|
+
*
|
|
1635
|
+
* DSH 0.1 answered this from `settings.get(namespace)`; 0.2 derives every
|
|
1636
|
+
* namespace from the entry that owns it, so the resolved value lives on that
|
|
1637
|
+
* entry's fiber (the same place the platform's settings service reads it).
|
|
1638
|
+
* A host without a config editor degrades to `undefined`, which is what this
|
|
1639
|
+
* plugin's provider refinement has always done when a namespace was absent.
|
|
1640
|
+
*/
|
|
1601
1641
|
function readNamespace(ctx, namespace) {
|
|
1602
|
-
const
|
|
1603
|
-
if (
|
|
1642
|
+
const editor = ctx.get("configEditor");
|
|
1643
|
+
if (typeof editor?.entries !== "function") return void 0;
|
|
1604
1644
|
try {
|
|
1605
|
-
|
|
1645
|
+
const entry = editor.entries().find((candidate) => candidate.options?.id === namespace);
|
|
1646
|
+
return entry?.fiber?.config === void 0 ? void 0 : plainConfig(entry.fiber.config);
|
|
1606
1647
|
} catch {
|
|
1607
1648
|
return;
|
|
1608
1649
|
}
|
|
@@ -1675,7 +1716,7 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1675
1716
|
const catalog = toSourceCatalog(ALL_SOURCES);
|
|
1676
1717
|
const lookup = createCredentialLookup(ctx, deps.credentialFallback ?? {});
|
|
1677
1718
|
let config = DEFAULT_CONFIG;
|
|
1678
|
-
installUsageStateSettings(ctx, (next) => {
|
|
1719
|
+
installUsageStateSettings(ctx, deps.config, (next) => {
|
|
1679
1720
|
config = next;
|
|
1680
1721
|
});
|
|
1681
1722
|
/**
|
|
@@ -1757,8 +1798,12 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1757
1798
|
store.start();
|
|
1758
1799
|
return provideUsageState(ctx, service);
|
|
1759
1800
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1801
|
+
/**
|
|
1802
|
+
* Cordis entry point. The second argument is the loader's resolved config for this
|
|
1803
|
+
* entry — a live reference, because the exported schema is a volatile root.
|
|
1804
|
+
*/
|
|
1805
|
+
function apply(ctx, config) {
|
|
1806
|
+
createUsageState(ctx, { config });
|
|
1762
1807
|
}
|
|
1763
1808
|
//#endregion
|
|
1764
|
-
export { apply, createCredentialLookup, createUsageState, inject, name, provideUsageState };
|
|
1809
|
+
export { Config, apply, createCredentialLookup, createUsageState, inject, isVolatileRef, name, plainConfig, provideUsageState };
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-usage-state",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH plugin: show account balance (API mode) or coding-plan quota (5h/7d/30d) for the model in use",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20",
|
|
9
|
-
"dsh": ">=0.
|
|
9
|
+
"dsh": ">=0.2.0-rc.2 <0.3.0-0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "node --import ./tests/support/client-render-hook.mjs --test",
|
|
@@ -28,12 +28,15 @@
|
|
|
28
28
|
"./package.json": "./package.json"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@deepseek-ai/
|
|
32
|
-
"@deepseek-ai/dsh-client-
|
|
33
|
-
"@deepseek-ai/dsh-client-
|
|
34
|
-
"@deepseek-ai/dsh-client-ui-
|
|
35
|
-
"@deepseek-ai/dsh-client-ui-
|
|
36
|
-
"@deepseek-ai/dsh-
|
|
31
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
32
|
+
"@deepseek-ai/dsh-client-locale": "^0.2.0-rc.2",
|
|
33
|
+
"@deepseek-ai/dsh-client-store": "^0.2.0-rc.2",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.2",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.2",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
|
|
37
|
+
"@deepseek-ai/dsh-typert-loader": "^0.2.0-rc.2",
|
|
38
|
+
"@deepseek-ai/dsh-typert-registry": "^0.2.0-rc.2",
|
|
39
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
37
40
|
"@types/node": "^24.0.0",
|
|
38
41
|
"@types/react": "~18.3.1",
|
|
39
42
|
"@types/react-dom": "~18.3.1",
|
|
@@ -43,6 +46,10 @@
|
|
|
43
46
|
"typescript": "^5.9.2",
|
|
44
47
|
"unrun": "^0.2.39"
|
|
45
48
|
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@deepseek-ai/dsh-settings": "^0.2.0-rc.2",
|
|
51
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
52
|
+
},
|
|
46
53
|
"dependencies": {
|
|
47
54
|
"zod": "4.5.1"
|
|
48
55
|
},
|