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 +21 -0
- package/README.md +176 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +757 -0
- package/lib/index.js +365 -0
- package/package.json +43 -4
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
|
-
#
|
|
1
|
+
# dsh-consumption-badge
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/cordis.patch.yml
ADDED