@cassius0924/dsh-usage-dashboard 0.3.1 → 0.5.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.
@@ -136,6 +136,56 @@ export interface SessionCost {
136
136
  /** Epoch ms of the last usage record in this session. */
137
137
  lastActive: number;
138
138
  }
139
+ /**
140
+ * What the local log replay could actually inspect. Totals can otherwise look
141
+ * authoritative even when one corrupt session was skipped, so this metadata
142
+ * travels with every usage response and lets the UI disclose partial results.
143
+ */
144
+ export interface UsageCoverage {
145
+ scope: 'local-dsh-session-logs';
146
+ /** Session headers with a usable id returned by the local persistence. */
147
+ listedSessions: number;
148
+ /** Session logs read successfully, including logs with no usage. */
149
+ scannedSessions: number;
150
+ /** Valid assistant usage records folded into the totals. */
151
+ usageRecords: number;
152
+ /** Usage records rejected because their time or token data was invalid. */
153
+ skippedRecords: number;
154
+ /** Session logs that could not be read at all. */
155
+ failedSessions: number;
156
+ /** Epoch ms of the first/last valid usage record across all local logs. */
157
+ earliestAt: number | null;
158
+ latestAt: number | null;
159
+ }
160
+ export declare const USAGE_WINDOW_DAYS: readonly [7, 30, 90, 365];
161
+ export type UsageWindowDays = (typeof USAGE_WINDOW_DAYS)[number];
162
+ /**
163
+ * Session ids observed in this codebase (host-issued, e.g.
164
+ * `session-484a1c14-c6fe-4d6a-abfd-a2d8d2f664d5`) are always plain
165
+ * ASCII-safe tokens — no documented format contract exists for them outside
166
+ * this repo (`SessionPersistenceFace`/`readFrom` is implemented by the DSH
167
+ * host, not here), so this is a defensive charset whitelist rather than an
168
+ * exact-format check: letters, digits, `-`, `_` only, with a generous length
169
+ * cap. It exists specifically to stop path-separator / `..` / null-byte
170
+ * payloads from ever reaching `persistence.readFrom(id, 0)` — `id` on the
171
+ * `/session` route arrives verbatim from the browser-controlled `?id=` query
172
+ * parameter, unlike `fetchUsage()`'s session ids, which the host itself
173
+ * enumerates via `persistence.list()`.
174
+ */
175
+ export declare const SESSION_ID_PATTERN: RegExp;
176
+ export declare const isValidSessionId: (id: string) => boolean;
177
+ /** One internally consistent view of the same local logs over a calendar window. */
178
+ export interface UsageWindow {
179
+ days: UsageWindowDays;
180
+ daily: DailyUsage[];
181
+ hourly: HourlyUsage[];
182
+ totals: UsageTotals;
183
+ models: ModelUsage[];
184
+ /** Most expensive sessions inside this window, capped at SESSION_TOP_N. */
185
+ sessions: SessionCost[];
186
+ /** All sessions that contributed usage inside this window. */
187
+ sessionCount: number;
188
+ }
139
189
  export interface UsageData {
140
190
  daily: DailyUsage[];
141
191
  hourly: HourlyUsage[];
@@ -149,9 +199,63 @@ export interface UsageData {
149
199
  sessions: SessionCost[];
150
200
  /** How many sessions contributed usage in total. */
151
201
  sessionCount: number;
202
+ coverage: UsageCoverage;
203
+ windows: UsageWindow[];
152
204
  }
153
205
  export interface UsageResponse {
154
206
  ok: boolean;
155
207
  error?: string;
156
208
  data?: UsageData;
157
209
  }
210
+ /**
211
+ * One model's contribution to a single session's bill — the same field
212
+ * shape as `ModelUsage` minus the `daily`/`hourly` series, which only make
213
+ * sense aggregated across many sessions.
214
+ */
215
+ export interface SessionModelUsage {
216
+ provider: string;
217
+ model: string;
218
+ input: number;
219
+ output: number;
220
+ cache: number;
221
+ total: number;
222
+ cost: number;
223
+ calls: number;
224
+ }
225
+ /** One completed conversation turn's usage. `messageId` is the durable id of
226
+ * the turn's final assistant message — the same identity the chat action-strip
227
+ * slot exposes to client plugins. Tool-call steps inside the turn are folded
228
+ * into this one figure rather than shown as misleading separate charges. */
229
+ export interface TurnUsage {
230
+ messageId: string;
231
+ total: number;
232
+ cost: number;
233
+ calls: number;
234
+ }
235
+ /**
236
+ * Usage for exactly one session — what the 「额度」tab's own conversation
237
+ * is costing, as opposed to `UsageData`'s account-wide totals. Fetched on
238
+ * demand (see `fetchSessionUsage`), not part of the 5-minute `usage` memo:
239
+ * a single session read is cheap and must reflect the session's live state.
240
+ */
241
+ export interface SessionUsageData {
242
+ sessionId: string;
243
+ /** The session's own title from its log; falls back to a short id. */
244
+ title: string;
245
+ total: number;
246
+ cost: number;
247
+ calls: number;
248
+ /** Epoch ms of the first/last valid usage record in this session, or null
249
+ * when the session has produced no usage yet. */
250
+ firstActive: number | null;
251
+ lastActive: number | null;
252
+ /** Most expensive model first. */
253
+ models: SessionModelUsage[];
254
+ /** Completed turns, in log order, addressed by their final message id. */
255
+ turns: TurnUsage[];
256
+ }
257
+ export interface SessionUsageResponse {
258
+ ok: boolean;
259
+ error?: string;
260
+ data?: SessionUsageData;
261
+ }
@@ -2,12 +2,15 @@
2
2
  * dsh-usage-dashboard host plugin.
3
3
  *
4
4
  * Registers the package's own fenced JSON API under
5
- * `/api/dsh-usage-dashboard` (balance + usage), consumed by the client half
6
- * through same-origin fetch. Both endpoints are memoized with a TTL so that
7
- * tab switches and the widget's polling don't replay session logs or hammer
8
- * the DeepSeek API; responses carry `Cache-Control` so the browser's HTTP
9
- * cache serves repeat requests without a network round trip. `?refresh=1`
10
- * bypasses the memo for an explicit force refresh.
5
+ * `/api/dsh-usage-dashboard` (balance + usage + session), consumed by the
6
+ * client half through same-origin fetch. `balance` and `usage` are memoized
7
+ * with a TTL so that tab switches and the widget's polling don't replay
8
+ * session logs or hammer the DeepSeek API; responses carry `Cache-Control`
9
+ * so the browser's HTTP cache serves repeat requests without a network
10
+ * round trip. `?refresh=1` bypasses the memo for an explicit force refresh.
11
+ * `session` (usage for exactly one session, keyed by `?id=`) reads a single
12
+ * session log and is deliberately not memoized — it is cheap and must
13
+ * reflect that session's live state, not a 5-minute-stale snapshot.
11
14
  */
12
15
  import type { HostContext } from './context.ts';
13
16
  /** Plugin identity for the cordis.patch.yml row (and the client bundle id). */
@@ -1,12 +1,12 @@
1
- /**
2
- * Balance and usage data sources.
3
- *
4
- * - Balance: DeepSeek API `GET /user/balance` with the key resolved through
5
- * the credentials service (`DEEPSEEK_API_KEY`).
6
- * - Usage: token usage folded from the DSH session logs (`sessionPersistence`),
7
- * one record per `assistant/message` event, bucketed by local day / hour.
8
- */
9
- import type { BalanceResponse, UsageResponse } from './contract.ts';
1
+ import type { BalanceResponse, SessionUsageResponse, UsageResponse } from './contract.ts';
10
2
  import type { CredentialsFace, SessionPersistenceFace } from './context.ts';
11
3
  export declare function fetchBalance(credentials: CredentialsFace | undefined): Promise<BalanceResponse>;
12
- export declare function fetchUsage(persistence: SessionPersistenceFace | undefined): Promise<UsageResponse>;
4
+ export declare function fetchUsage(persistence: SessionPersistenceFace | undefined, nowMs?: number): Promise<UsageResponse>;
5
+ /**
6
+ * Usage for exactly one session — the 「额度」tab's own conversation, as
7
+ * opposed to `fetchUsage`'s account-wide replay. Reads a single session log
8
+ * (not every session on disk), so it is cheap enough to call on every tab
9
+ * mount without a TTL memo; the caller is expected to re-fetch whenever the
10
+ * session id it cares about changes.
11
+ */
12
+ export declare function fetchSessionUsage(persistence: SessionPersistenceFace | undefined, sessionId: string): Promise<SessionUsageResponse>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cassius0924/dsh-usage-dashboard",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "DeepSeek 额度与用量仪表盘 —— DSH (DeepSeek Harness) Web 插件:余额还能撑几天、今日/本月花了多少、哪个模型和哪个会话最贵、缓存省了多少,以及 2026-08-17 峰谷定价后的账单预估",
5
5
  "keywords": [
6
6
  "deepseek",
@@ -45,6 +45,7 @@
45
45
  "immediately": true,
46
46
  "inject": [
47
47
  "@deepseek-ai/dsh-client-runtime",
48
+ "@deepseek-ai/dsh-client-locale",
48
49
  "@deepseek-ai/dsh-client-ui-conversation"
49
50
  ]
50
51
  }
@@ -57,7 +58,8 @@
57
58
  ],
58
59
  "scripts": {
59
60
  "build": "node build.mjs",
60
- "typecheck": "tsc --noEmit",
61
+ "test": "node test/run.mjs",
62
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
61
63
  "prepare": "node build.mjs"
62
64
  },
63
65
  "license": "MIT",