dsh-consumption-badge 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zlc13
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,177 @@
1
- # Temporary Holding Version
1
+ # dsh-consumption-badge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A **persistent** DSH (DeepSeek Harness) Web UI plugin that puts a live consumption badge
4
+ next to every assistant message, and opens a detail panel with the session's spend curve.
5
+
6
+ ```
7
+ ⛽ 25.6k | ≈¥0.020 | valley
8
+ ```
9
+
10
+ - **Collapsed pill** – session-cumulative tokens, estimated cost (always prefixed `≈`), and the
11
+ current peak/valley tier (red `peak`, green `valley`), injected into
12
+ `conversation.chat.assistant-actions` right after the shipped copy/branch controls.
13
+ - **Detail modal** (centered, blurred backdrop, `Esc` or backdrop-click to close):
14
+ cumulative tokens with hit/miss/output split, estimated cost, current pricing tier,
15
+ DeepSeek account balance, and a **per-turn dual-axis line chart**
16
+ (left axis: tokens, right axis: ¥) with a hover tooltip showing the model,
17
+ input-hit / input-miss / output tokens, and the turn's peak/valley flag.
18
+
19
+ Everything is an **estimate** for reference only — this plugin is not a billing system of record.
20
+
21
+ ## Requirements
22
+
23
+ | Capability | Why |
24
+ | --- | --- |
25
+ | DSH `0.1.0-rc.7` or compatible | slot API `conversation.chat.assistant-actions`, `shell.overlay` |
26
+ | `DEEPSEEK_API_KEY` in the DSH credential store | account balance (`GET /user/balance`) |
27
+ | `curl` on `PATH` | the balance request; on Windows 10+ it ships as `C:\Windows\System32\curl.exe` |
28
+
29
+ The API key is resolved **per request** through the Cordis `credentials` service and passed to
30
+ `curl` on **stdin** (`curl -K -`), never through argv, and never reaches the browser.
31
+
32
+ If the key or `curl` is missing, only the balance card degrades (it shows the reason);
33
+ tokens, cost and the chart keep working.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ dsh plugin --profile web add dsh-consumption-badge
39
+ ```
40
+
41
+ Then **restart the DSH process** — a host-side plugin (and the browser plugin roster) is composed
42
+ at process start, so a page refresh is not enough.
43
+
44
+ Verify:
45
+
46
+ ```bash
47
+ cd "$DSH_HOME/profiles/web"
48
+ dsh --profile web --dump-config | grep consumption-badge # expect exactly one row
49
+ # after restart:
50
+ curl -s http://127.0.0.1:<port>/api/consumption/account # expect {"ok":true,...,"model":"..."}
51
+ curl -s -o /dev/null -w '%{http_code}\n' \
52
+ http://127.0.0.1:<port>/plugins/dsh-consumption-badge/client.js # expect 200
53
+ ```
54
+
55
+ ### If the row does not mount
56
+
57
+ Some deployments compose `dsh.profile.bundles` into `--dump-config` without mounting those rows
58
+ at runtime. The reliable path is a roster row in the **profile's own patch layer** — add it to
59
+ `$DSH_HOME/profiles/web/cordis.patch.yml` and remove `dsh-consumption-badge` from
60
+ `dsh.profile.bundles` so the id is inserted exactly once:
61
+
62
+ ```yaml
63
+ - insert:
64
+ - id: consumption-badge
65
+ name: 'dsh-consumption-badge'
66
+ ```
67
+
68
+ The package also ships its own `cordis.patch.yml` (`dsh.bundle.patch`) with the same row for
69
+ deployments where the bundle layer does mount. Keep only **one** of the two active.
70
+
71
+ ## What it reads
72
+
73
+ | Data | Source |
74
+ | --- | --- |
75
+ | Per-turn tokens | `assistant/message.usage` events in the session log, folded per turn (`cacheReadTokens` → hit, `inputTokens + cacheWriteTokens` → miss, `outputTokens` → output; multi-step turns are summed) |
76
+ | Turn timing | `turn/start` event time (peak/valley is attributed to **request start**) |
77
+ | Model per turn | `request/context` / `request/header.config`; falls back to the agent default model, then `deepseek-flash` |
78
+ | Session totals | the shipped `tokenUsage` projection when present, otherwise the folded turns |
79
+ | Balance | `GET https://api.deepseek.com/user/balance` via curl, 60s cache |
80
+
81
+ Turn records are also mirrored into browser `localStorage` under
82
+ `dsh-consumption:<sessionId>:<turn>` so the chart survives reloads. Persistence writes are
83
+ scheduled off the render path (`requestIdleCallback`), and the aggregation is O(turns).
84
+
85
+ ## Pricing model
86
+
87
+ Off-peak prices in **CNY per 1M tokens**; peak multiplies everything by **2**.
88
+
89
+ | Model | input cache hit | input cache miss | output |
90
+ | --- | --- | --- | --- |
91
+ | `deepseek-flash` | 0.02 | 1 | 4 |
92
+ | `deepseek-v4-pro` | 0.15 | 4.5 | 13.5 |
93
+
94
+ ```
95
+ cost = (hit/1e6)*hit_price + (miss/1e6)*miss_price + (out/1e6)*out_price [×2 at peak]
96
+ ```
97
+
98
+ **Peak** = Beijing time (UTC+8), Monday–Friday, 09:00–12:00 and 14:00–18:00, excluding statutory
99
+ holidays. Everything else, plus weekends, is **valley**.
100
+
101
+ Unrecognised model ids are priced as `deepseek-flash` (or `deepseek-v4-pro` when the id contains
102
+ `pro`) and the UI marks the figure as an estimate. The table lives in `PRICES` at the top of
103
+ `lib/client.js` — fork and edit to follow a price change.
104
+
105
+ The fixed-date holiday list (`CN_FIXED_HOLIDAYS`, `MM-DD`) covers New Year, Labour Day and the
106
+ first three days of National Day. Lunar holidays (Spring Festival, Qingming, Dragon Boat,
107
+ Mid-Autumn) must be added by hand.
108
+
109
+ ## HTTP surface
110
+
111
+ The host half registers three exact, read-only routes on the DSH web server:
112
+
113
+ | Route | Returns |
114
+ | --- | --- |
115
+ | `GET /api/consumption/turns?sessionId=<id>` | per-turn `{turn, hit, miss, out, time, model}` (log-length cached) |
116
+ | `GET /api/consumption/account` | current provider/model + whether the key is configured |
117
+ | `GET /api/consumption/balance[?force=1]` | `{ok, available, currency, total, granted, toppedUp}`; `is_available:false` renders a red “insufficient balance” |
118
+
119
+ These routes are **unauthenticated**. If your DSH web server binds `0.0.0.0`, anyone on the
120
+ network can read the balance and token figures; keep it on `127.0.0.1` unless you need otherwise.
121
+
122
+ ## Prompt for your AI agent (copy-paste)
123
+
124
+ Paste this into Claude Code / Codex / DSH and let it do the install and verification:
125
+
126
+ ```text
127
+ Install the DSH plugin "dsh-consumption-badge" into my web profile and prove it works.
128
+
129
+ 1. In $DSH_HOME/profiles/web run: `dsh plugin --profile web add dsh-consumption-badge`.
130
+ (For a local checkout use `dsh plugin --profile web add "file:<abs path to the package>"`.)
131
+ 2. Check `dsh --profile web --dump-config` contains exactly ONE `id: consumption-badge` row.
132
+ `dsh plugin add` also appends the package to `dsh.profile.bundles`; if that row does not
133
+ actually mount after the restart, move it: remove it from `dsh.profile.bundles` and add
134
+ `- insert: [{id: consumption-badge, name: 'dsh-consumption-badge'}]` to the profile's
135
+ `cordis.patch.yml` instead. Never leave both.
136
+ 3. Restart the DSH process (a browser refresh is NOT enough — host plugins are composed at boot).
137
+ 4. Verify the host half: `curl -s http://127.0.0.1:<port>/api/consumption/account` must return
138
+ `{"ok":true,...}`. Verify the client half:
139
+ `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:<port>/plugins/dsh-consumption-badge/client.js`
140
+ must return `200`.
141
+ 5. Open any session and confirm the `⛽ <tokens> | ≈¥<cost> | peak|valley` pill in the assistant
142
+ action row; click it and confirm the modal shows the four cards, the dual-axis chart, the
143
+ balance, and that Esc closes it.
144
+ 6. Report which step failed and paste the boot log lines that mention `consumption-badge`.
145
+
146
+ Rules while doing this: never write to the DSH credentials file yourself (ask me for
147
+ DEEPSEEK_API_KEY), never bind a second server to the same port, and do not try to persist the
148
+ plugin by "running" it — a session-scoped dynamic plugin always dies with the process.
149
+ ```
150
+
151
+ ## Notes for AI agents
152
+
153
+ - This is a **profile-level** plugin: `package.json` carries `dsh.bundle.patch` and `dsh.client`
154
+ (`platform: web`). It cannot be shipped as a dynamic/session plugin — `harness.handle` and
155
+ `host.call` are dynamic-runner-only and do not exist for a bundled plugin; the client half talks
156
+ to the host half over the same-origin routes listed above.
157
+ - The client bundle is hand-written in `window.__ModuleLoader__.load({id, factory})` form and only
158
+ `require("react")` — React must stay external (a second React instance breaks slot rendering).
159
+ - `lib/index.js` imports nothing at all (no `@deepseek-ai/cordis`), so it does not depend on the
160
+ profile being able to resolve Cordis; it exports plain `name` / `inject` / `apply`.
161
+ - Self-check without booting DSH: `node check.mjs` (46 assertions over the route contract and the
162
+ client bundle contract). It lives outside the published `files` list.
163
+
164
+ ## Known limitations
165
+
166
+ - All amounts are **estimates** — price table, peak/valley rules and model detection can drift
167
+ from your actual invoice.
168
+ - One chart point per turn; multiple steps in a turn are summed.
169
+ - Balance parsing depends on DeepSeek's `/user/balance` response shape; failures only affect the
170
+ balance card.
171
+ - The chart covers turns present in the session log fold (whole log), while the pill's total uses
172
+ the shipped `tokenUsage` projection — both are whole-session, but a heavily-compacted session may
173
+ differ by the tokens lost to compaction.
174
+
175
+ ## License
176
+
177
+ MIT
@@ -0,0 +1,7 @@
1
+ # dsh-consumption-badge —— bundle patch
2
+ # 本插件作为 profile 的 bundle 层被挂载:profile 的 package.json 把它列进
3
+ # dsh.profile.bundles 后,这一行 insert 就会把它插进配置树。
4
+ # `name:` 必须与安装后的包名完全一致,否则表现为「装上了但界面上没有」。
5
+ - insert:
6
+ - id: consumption-badge
7
+ name: dsh-consumption-badge