dsh-context 0.58.0 → 0.59.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/lib/index.d.ts CHANGED
@@ -8,7 +8,13 @@ interface ActivityState {
8
8
  days: Record<string, {
9
9
  tokens: number;
10
10
  requests: number;
11
+ cost?: SessionCostUsage;
11
12
  }>;
13
+ /** The route in force (last `request/header`'s config wins) — prices each settlement. */
14
+ model?: string;
15
+ provider?: string;
16
+ /** The open step's start instant (armed by `step/start`, cleared by `step/end`). */
17
+ stepStart?: number;
12
18
  }
13
19
  //#endregion
14
20
  //#region src/host/headers.d.ts
@@ -299,6 +305,16 @@ interface ActivityDay {
299
305
  tokens: number;
300
306
  /** Completed model calls (assistant settlements) that day. */
301
307
  requests: number;
308
+ /**
309
+ * The day's billed buckets keyed provider → model → pricing period — the
310
+ * same SessionCostUsage raw material the timeline's session-cost totals
311
+ * carry, scoped to the day the requests INITIATED in, so the client prices
312
+ * each day off the same model-price book as the KPI band. ADDITIVE-OPTIONAL:
313
+ * absent on rows folded before the field existed (older plugin builds), on
314
+ * days whose settlements predate any model header, and on settlements the
315
+ * provider left unmetered — the usage chart degrades to tokens-only bars.
316
+ */
317
+ cost?: SessionCostUsage;
302
318
  }
303
319
  /**
304
320
  * The per-session daily activity ledger (`contextActivity` wire value):
package/lib/index.js CHANGED
@@ -1056,6 +1056,10 @@ function tokenCountOf(value) {
1056
1056
  * DeepSeek's peak windows (the official list: UTC 01:00–04:00 and 06:00–10:00,
1057
1057
  * Monday through Friday — Beijing Time 09:00–12:00 and 14:00–18:00). All other
1058
1058
  * hours, plus entire weekends, bill at the half-price off-peak rate.
1059
+ *
1060
+ * Exported for the activity unit (host/activity.ts): the daily ledger prices
1061
+ * each day's buckets with the same split, so its per-day fees sum to the
1062
+ * session-cost totals the timeline fold books.
1059
1063
  */
1060
1064
  function isPeakUtc(time) {
1061
1065
  const at = new Date(time);
@@ -1744,7 +1748,8 @@ function dayKeyOf(time) {
1744
1748
  //#region src/host/activity.ts
1745
1749
  /**
1746
1750
  * The `contextActivity` session projection unit — the per-day activity
1747
- * ledger behind the Context Dashboard's heatmap.
1751
+ * ledger behind the Context Dashboard's heatmap and its last-7-days usage
1752
+ * chart.
1748
1753
  *
1749
1754
  * The timeline unit (fold.ts) is a "current snapshot" fold: it prices the
1750
1755
  * context as it stands NOW, which cannot draw a per-day chart. This unit
@@ -1753,75 +1758,165 @@ function dayKeyOf(time) {
1753
1758
  * completed request to its day and, when the provider reported a usage
1754
1759
  * object, the day's billed-token figure grows by the disjoint buckets
1755
1760
  * (uncached input + cache read + cache write + output — the same semantics
1756
- * the timeline's request records and the official token meter use). The
1757
- * overview merges every listed session's ledger off the session list's
1758
- * projection column (no per-session open needed) and colors its heatmap.
1761
+ * the timeline's request records and the official token meter use). Each
1762
+ * metered settlement also books its buckets into the day's per-(provider,
1763
+ * model, period) pricing record — the same raw material the timeline's
1764
+ * session-cost totals carry (SessionCostUsage), so the client prices every
1765
+ * day off the SAME model-price book and estimator it prices the KPI band
1766
+ * with. Buckets attribute to the day the open STEP STARTED (the turn's
1767
+ * initiation, armed by `step/start` and cleared by `step/end` — the
1768
+ * timeline fold's own pending-slot protocol), falling back to the
1769
+ * settlement instant when no stamp is armed; the peak/off split prices the
1770
+ * same instant, so a fee always lands on one day at one rate.
1759
1771
  *
1760
- * One wire contract, one small state: a day entry is two integers and the
1761
- * retention cap keeps at most {@link MAX_KEPT_DAYS} keys, so the value
1762
- * riding every session-list row stays trivial next to the timeline head.
1763
- * Same projection contract as the sibling units: pure init/apply/view,
1764
- * unknown or malformed events return the state unchanged, and no
1765
- * `undefined`-valued property ever materializes (the plain-JSON persisted-
1766
- * state precondition).
1772
+ * One wire contract, one small state: a day entry is two integers plus the
1773
+ * optional pricing record, the route in force rides the state (last
1774
+ * `request/header` wins), and the retention cap keeps at most
1775
+ * {@link MAX_KEPT_DAYS} keys, so the value riding every session-list row
1776
+ * stays trivial next to the timeline head. Same projection contract as the
1777
+ * sibling units: pure init/apply/view, unknown or malformed events return
1778
+ * the state unchanged, and no `undefined`-valued property ever materializes
1779
+ * (the plain-JSON persisted-state precondition).
1767
1780
  */
1768
1781
  /**
1769
1782
  * Retention cap on ledger days. A little over a year of daily-active
1770
1783
  * sessions; the oldest keys drop first (key order IS chronological order).
1771
1784
  */
1772
1785
  const MAX_KEPT_DAYS = 400;
1786
+ const costBucketSchema = z.object({
1787
+ uncached: z.number().int().nonnegative(),
1788
+ cacheRead: z.number().int().nonnegative(),
1789
+ cacheWrite: z.number().int().nonnegative(),
1790
+ output: z.number().int().nonnegative()
1791
+ }).strict();
1792
+ const costModelSchema$1 = z.object({
1793
+ peak: costBucketSchema.optional(),
1794
+ off: costBucketSchema.optional()
1795
+ }).strict();
1796
+ const costUsageSchema$1 = z.record(z.string(), z.record(z.string(), costModelSchema$1));
1773
1797
  const activityDaySchema = z.object({
1774
1798
  tokens: z.number().int().nonnegative(),
1775
- requests: z.number().int().nonnegative()
1799
+ requests: z.number().int().nonnegative(),
1800
+ cost: costUsageSchema$1.optional()
1776
1801
  }).strict();
1777
1802
  /** Validate the wire payload before it leaves the host (strict: no drift). */
1778
1803
  const contextActivitySchema = z.object({ days: z.record(z.string(), activityDaySchema) }).strict();
1779
- /** The persisted fold-state schema — identical shape, validated before a checkpoint row seeds a fold. */
1780
- const activityStateSchema = z.object({ days: z.record(z.string(), activityDaySchema) });
1781
- /**
1782
- * The day's billed increment from one durable usage object: the four
1783
- * disjoint buckets summed after the shared per-bucket sanitizer
1784
- * ({@link tokenCountOf} — fractions round, negatives clamp, garbage reads
1785
- * absent). A usage object with NO readable bucket contributes nothing, so a
1786
- * settlement that carried no usage still counts its request without
1787
- * fabricating tokens.
1788
- */
1789
- function billedOfUsage(value) {
1790
- if (value === null || typeof value !== "object") return 0;
1804
+ /** The persisted fold-state schema — validated before a checkpoint row seeds a fold. */
1805
+ const activityStateSchema = z.object({
1806
+ days: z.record(z.string(), activityDaySchema),
1807
+ model: z.string().optional(),
1808
+ provider: z.string().optional(),
1809
+ stepStart: z.number().optional()
1810
+ });
1811
+ /**
1812
+ * One durable usage object's billed buckets, or null when NO bucket is
1813
+ * readable (the same rule the timeline fold bills by: a settlement that
1814
+ * carried no usage still counts its request without fabricating tokens).
1815
+ * Every bucket passes the shared per-bucket sanitizer ({@link tokenCountOf}
1816
+ * — fractions round, negatives clamp, garbage reads absent).
1817
+ */
1818
+ function billedBucketsOf(value) {
1819
+ if (value === null || typeof value !== "object") return null;
1791
1820
  const usage = value;
1792
- let any = false;
1793
- let total = 0;
1794
- for (const bucket of [
1795
- usage.inputTokens,
1796
- usage.cacheReadTokens,
1797
- usage.cacheWriteTokens,
1798
- usage.outputTokens
1799
- ]) {
1800
- const count = tokenCountOf(bucket);
1801
- if (count !== null) {
1802
- total += count;
1803
- any = true;
1821
+ const input = tokenCountOf(usage.inputTokens);
1822
+ const cacheRead = tokenCountOf(usage.cacheReadTokens);
1823
+ const cacheWrite = tokenCountOf(usage.cacheWriteTokens);
1824
+ const output = tokenCountOf(usage.outputTokens);
1825
+ if (input === null && cacheRead === null && cacheWrite === null && output === null) return null;
1826
+ return {
1827
+ input: input ?? 0,
1828
+ cacheRead: cacheRead ?? 0,
1829
+ cacheWrite: cacheWrite ?? 0,
1830
+ output: output ?? 0
1831
+ };
1832
+ }
1833
+ /**
1834
+ * Add one settlement's billed buckets to a day's pricing record — the same
1835
+ * clone-along-the-mutated-path walk the timeline fold's accumulateCost
1836
+ * prices by, so the two folds' records share one shape and one semantics.
1837
+ * The pricing period splits off the SAME instant the day bucket does: a fee
1838
+ * can never land on one day at the neighbouring window's rate.
1839
+ */
1840
+ function pricedDayOf(prev, provider, model, period, usage) {
1841
+ const models = prev?.[provider] ?? {};
1842
+ const periods = models[model] ?? {};
1843
+ const b = periods[period] ?? {
1844
+ uncached: 0,
1845
+ cacheRead: 0,
1846
+ cacheWrite: 0,
1847
+ output: 0
1848
+ };
1849
+ return {
1850
+ ...prev,
1851
+ [provider]: {
1852
+ ...models,
1853
+ [model]: {
1854
+ ...periods,
1855
+ [period]: {
1856
+ uncached: b.uncached + usage.input,
1857
+ cacheRead: b.cacheRead + usage.cacheRead,
1858
+ cacheWrite: b.cacheWrite + usage.cacheWrite,
1859
+ output: b.output + usage.output
1860
+ }
1861
+ }
1804
1862
  }
1805
- }
1806
- return any ? total : 0;
1863
+ };
1807
1864
  }
1808
1865
  /**
1809
- * Fold one committed event into the ledger. Only `assistant/message` (the
1810
- * step settlement — both supported log generations) advances a day; every
1811
- * other type returns the state reference unchanged, as does a settlement
1812
- * with an unreadable time. The next state is a copy along the mutated path
1813
- * only — the persisted previous state is never mutated in place.
1866
+ * Fold one committed event into the ledger. Four event types advance it:
1867
+ * `request/header` tracks the route in force (the timeline fold's rule —
1868
+ * last header wins), `step/start`/`step/end` maintain the initiation stamp,
1869
+ * and `assistant/message` (the step settlement — both supported log
1870
+ * generations) books the day. Everything else returns the state reference
1871
+ * unchanged, as does a settlement with no attributable instant. The next
1872
+ * state is a copy along the mutated path only — the persisted previous
1873
+ * state is never mutated in place.
1814
1874
  */
1815
1875
  function applyActivity(state, event) {
1876
+ if (event.type === "request/header") {
1877
+ const config = event.data?.header?.config;
1878
+ if (config === null || typeof config !== "object") return state;
1879
+ const model = typeof config.model === "string" ? config.model : void 0;
1880
+ const provider = typeof config.provider === "string" ? config.provider : void 0;
1881
+ const nextModel = model !== void 0 ? model : state.model;
1882
+ const nextProvider = provider !== void 0 ? provider : state.provider;
1883
+ if (nextModel === state.model && nextProvider === state.provider) return state;
1884
+ return {
1885
+ ...state,
1886
+ ...nextModel !== void 0 ? { model: nextModel } : {},
1887
+ ...nextProvider !== void 0 ? { provider: nextProvider } : {}
1888
+ };
1889
+ }
1890
+ if (event.type === "step/start") {
1891
+ if (!Number.isFinite(event.time)) return state;
1892
+ return state.stepStart === event.time ? state : {
1893
+ ...state,
1894
+ stepStart: event.time
1895
+ };
1896
+ }
1897
+ if (event.type === "step/end") {
1898
+ if (state.stepStart === void 0) return state;
1899
+ const next = { ...state };
1900
+ delete next.stepStart;
1901
+ return next;
1902
+ }
1816
1903
  if (event.type !== "assistant/message") return state;
1817
- const key = dayKeyOf(event.time);
1904
+ const initiated = state.stepStart !== void 0 ? state.stepStart : event.time;
1905
+ const key = dayKeyOf(initiated);
1818
1906
  if (key === null) return state;
1819
1907
  const data = event.data;
1820
- const tokens = billedOfUsage(data?.usage);
1908
+ const buckets = billedBucketsOf(data?.usage);
1821
1909
  const prev = state.days[key];
1910
+ let cost = prev?.cost;
1911
+ if (buckets !== null && state.model !== void 0) {
1912
+ const provider = state.provider ?? "";
1913
+ const period = isDeepSeekProvider(provider) && !isPeakUtc(initiated) ? "off" : "peak";
1914
+ cost = pricedDayOf(cost, provider, state.model, period, buckets);
1915
+ }
1822
1916
  const entry = {
1823
- tokens: prev === void 0 ? tokens : prev.tokens + tokens,
1824
- requests: prev === void 0 ? 1 : prev.requests + 1
1917
+ tokens: (prev === void 0 ? 0 : prev.tokens) + (buckets === null ? 0 : buckets.input + buckets.cacheRead + buckets.cacheWrite + buckets.output),
1918
+ requests: (prev === void 0 ? 0 : prev.requests) + 1,
1919
+ ...cost !== void 0 ? { cost } : {}
1825
1920
  };
1826
1921
  let days = {
1827
1922
  ...state.days,
@@ -1833,12 +1928,19 @@ function applyActivity(state, event) {
1833
1928
  const drop = new Set(keys.slice(0, keys.length - MAX_KEPT_DAYS));
1834
1929
  days = Object.fromEntries(Object.entries(days).filter(([k]) => !drop.has(k)));
1835
1930
  }
1836
- return { days };
1931
+ return {
1932
+ ...state,
1933
+ days
1934
+ };
1837
1935
  }
1838
1936
  /**
1839
1937
  * The daily-activity projection unit, registered alongside the timeline and
1840
1938
  * headers units (host/index.ts); the overview reads it through the session
1841
- * list's projection column. `stateVersion` 1: the ledger's first shape.
1939
+ * list's projection column. `stateVersion` 2: the ledger gained the per-day
1940
+ * pricing records (and the fold the route/step-start tracking behind them),
1941
+ * so on upgrade every cached row reads version-stale — the dashboard's
1942
+ * warm-up cold-refolds it from the durable log, rebuilding the fees for the
1943
+ * whole corpus exactly.
1842
1944
  */
1843
1945
  function createContextActivityDefinition() {
1844
1946
  return {
@@ -1850,7 +1952,7 @@ function createContextActivityDefinition() {
1850
1952
  viewSchema: contextActivitySchema,
1851
1953
  view: (state) => ({ days: Object.fromEntries(Object.entries(state.days).map(([k, v]) => [k, { ...v }])) })
1852
1954
  },
1853
- stateVersion: 1
1955
+ stateVersion: 2
1854
1956
  };
1855
1957
  }
1856
1958
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "icon": "icon.svg",
6
6
  "author": "bowenliang123",
@@ -92,7 +92,8 @@
92
92
  "@deepseek-ai/dsh-session": ">=0.1.5-rc.1",
93
93
  "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1",
94
94
  "@deepseek-ai/schemastery": "^3.18.2",
95
- "react": "^18.3.1"
95
+ "react": "^18.3.1",
96
+ "react-dom": "^18.3.1"
96
97
  },
97
98
  "peerDependenciesMeta": {
98
99
  "@deepseek-ai/dsh-client-ui-primitives": {
@@ -100,13 +101,14 @@
100
101
  },
101
102
  "react": {
102
103
  "optional": true
104
+ },
105
+ "react-dom": {
106
+ "optional": true
103
107
  }
104
108
  },
105
109
  "devDependencies": {
106
110
  "@deepseek-ai/cordis": "^4.0.2",
107
111
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.1",
108
- "@deepseek-ai/dsh-invariants": "0.1.5-rc.1",
109
- "@deepseek-ai/dsh-scope": "0.1.5-rc.1",
110
112
  "@deepseek-ai/dsh-session": "0.1.5-rc.1",
111
113
  "@deepseek-ai/dsh-session-projection": "0.1.5-rc.1",
112
114
  "@deepseek-ai/dsh-settings": "0.1.5-rc.1",
@@ -117,15 +119,15 @@
117
119
  "@stylistic/eslint-plugin": "^5.10.0",
118
120
  "@tailwindcss/node": "4.3.3",
119
121
  "@tailwindcss/oxide": "4.3.3",
120
- "@types/node": "^26.2.0",
122
+ "@types/node": "^26.6.3",
121
123
  "@types/react": "^18.3.31",
122
124
  "@types/react-dom": "^18.3.7",
123
125
  "@vitest/coverage-v8": "^4.1.11",
124
126
  "anser": "^2.3.5",
125
127
  "clsx": "^2.0.0",
126
- "eslint-plugin-sonarjs": "^4.2.0",
128
+ "eslint-plugin-sonarjs": "^4.2.1",
127
129
  "husky": "^9.1.7",
128
- "jsdom": "^30.0.1",
130
+ "jsdom": "^30.1.1",
129
131
  "katex": "^0.16.47",
130
132
  "lightningcss": "1.32.0",
131
133
  "mdast-util-from-markdown": "^2.0.3",
@@ -139,17 +141,16 @@
139
141
  "micromark-util-classify-character": "^2.0.1",
140
142
  "micromark-util-sanitize-uri": "^2.0.1",
141
143
  "micromark-util-symbol": "^2.0.1",
142
- "oxlint": "1.82.0",
143
- "oxlint-tsgolint": "7.0.2001",
144
- "playwright-core": "^1.62.1",
144
+ "oxlint": "1.85.0",
145
+ "oxlint-tsgolint": "7.0.2003",
146
+ "playwright-core": "^1.63.0",
145
147
  "react": "^18.3.1",
146
148
  "react-dom": "^18.3.1",
147
149
  "shiki": "^4.3.1",
148
- "simple-icons": "16.31.0",
150
+ "simple-icons": "16.32.0",
149
151
  "tailwindcss": "4.3.3",
150
152
  "tsdown": "^0.23.0",
151
153
  "typescript": "^7.0.2",
152
- "vitest": "^4.1.11",
153
- "zod": "^4.4.3"
154
+ "vitest": "^4.1.11"
154
155
  }
155
156
  }