dsh-context 0.29.0 → 0.30.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/lib/index.d.ts CHANGED
@@ -2,29 +2,24 @@ import { ZodType, z } from "zod";
2
2
  import { Session, SessionEvent } from "@deepseek-ai/dsh-session";
3
3
  import { Context, Service } from "@deepseek-ai/cordis";
4
4
  //#region src/host/config.d.ts
5
- /** dsh-context host config. All fields optional; defaults below. */
6
5
  interface Config {
7
6
  /** Cap on kept per-step request records (the hard step backstop). */
8
7
  maxRequestSteps?: number;
9
8
  /** Newest whole-turn window kept; trimming crosses whole turns, never mid-turn. */
10
9
  maxKeptTurns?: number;
11
- /** Newest context-event records kept. */
12
10
  maxEvents?: number;
13
11
  /**
14
- * Surface nodes served to the browser (newest carry the signal; live
15
- * inject nodes are always served they are few and land first). The
16
- * default is deliberately generous: auto-compaction keeps the live
17
- * surface far below it in healthy sessions, so the browser effectively
18
- * lists EVERY live node; the bound stays as a backstop for pathological
19
- * sessions (every projection push ships the whole value, ~150B per node).
12
+ * Served surface nodes (newest carry the signal; live inject nodes are pinned — they land first and are few). Deliberately generous:
13
+ * auto-compaction keeps healthy surfaces far below it, so the browser effectively lists every live node; the bound is a
14
+ * pathological-session backstop (each push ships the whole value, ~150B/node).
20
15
  */
21
16
  maxNodes?: number;
22
17
  /** Removed (shadowed) surface nodes kept for per-step reconstruction. */
23
18
  maxArchiveNodes?: number;
24
19
  }
25
20
  /**
26
- * The cordis `Config` validator: strict on keys, defaults on the schema fields.
27
- * Tolerates `undefined` (a patch row without a `config:` block — defaults win).
21
+ * The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
22
+ * block — defaults win).
28
23
  */
29
24
  declare const Config: z.ZodPreprocess<z.ZodObject<{
30
25
  maxRequestSteps: z.ZodDefault<z.ZodNumber>;
@@ -36,19 +31,10 @@ declare const Config: z.ZodPreprocess<z.ZodObject<{
36
31
  //#endregion
37
32
  //#region src/shared/types.d.ts
38
33
  /**
39
- * Shared wire contract — the snapshot model exchanged between the Host and
40
- * Client halves.
41
- *
42
- * The Host half no longer serves this over a custom RPC channel: it is the
43
- * `view()` payload of the `contextTimeline` session projection, registered on
44
- * the harness's `ctx.sessionProjections` registry. The registry drives
45
- * `apply(state, event)` over every committed session event, persists the state
46
- * through `ctx.sessionProjectionCache`, and pushes the finished value to the
47
- * browser as a `session/projection` frame (with a tail-page baseline), where
48
- * the Client reads it through the framework-standard `useProjection` seat.
49
- *
50
- * TYPE-ONLY host-side module: both halves import these as `import type`, so
51
- * nothing from here ever reaches the runtime bundles.
34
+ * Shared wire contract — the snapshot model exchanged between the Host and Client halves. Delivered as the `view()` payload of the
35
+ * `contextTimeline`/`contextHeaders` session projections (registered on `ctx.sessionProjections`; the registry pushes finished views as
36
+ * `session/projection` frames — see host/timeline.ts). TYPE-ONLY host-side module: both halves import these as `import type`, so nothing
37
+ * from here ever reaches the runtime bundles.
52
38
  */
53
39
  declare module '@deepseek-ai/dsh-session-projection/types' {
54
40
  interface SessionProjectionMap {
@@ -70,7 +56,6 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
70
56
  contextHeaders: ContextHeaders;
71
57
  }
72
58
  }
73
- /** The five priced context categories (plus system/tools handled separately). */
74
59
  type Category = 'user' | 'inject' | 'assistant' | 'tool';
75
60
  interface Snapshot {
76
61
  ok: boolean;
@@ -93,15 +78,10 @@ interface Snapshot {
93
78
  * instead. Kept optional for wire compatibility with older clients.
94
79
  */
95
80
  occupancy?: {
96
- /** Provider-reported prompt size of the most recent request (input + cache). */
97
81
  pressureTokens?: number;
98
- /** Heuristic total over the current model-visible surface. */
99
82
  surfaceTokens: number;
100
- /** `surfaceTokens` at the newest usage sample. */
101
83
  sampledSurfaceTokens?: number;
102
- /** pressureTokens + surface movement since the sample (clamped ≥ 0). */
103
84
  projectedTokens?: number;
104
- /** Newest recorded route capacity (last-wins). */
105
85
  contextWindow?: number;
106
86
  };
107
87
  toolList: {
@@ -131,10 +111,9 @@ interface Snapshot {
131
111
  */
132
112
  cost?: SessionCostUsage;
133
113
  /**
134
- * The served live surface: the newest `maxNodes` tail PLUS every live
135
- * inject node older than the tail (injections land first and are few, so
136
- * they are pinned otherwise a long session would price them while the
137
- * browser could list none). Seq-ordered, oldest first.
114
+ * The served live surface: the newest `maxNodes` tail PLUS every live inject node older than the tail (injections land first and are
115
+ * few,
116
+ * so they are pinned). Seq-ordered, oldest first.
138
117
  */
139
118
  nodes: SurfaceNode[];
140
119
  /** Live nodes not served (the overflow beyond `maxNodes`, minus pinned injects — see `nodes`). */
@@ -159,11 +138,8 @@ interface Snapshot {
159
138
  archiveFloor?: number;
160
139
  }
161
140
  /**
162
- * The `contextTimeline` projection's whole value — the same snapshot the
163
- * Client has always rendered, now delivered through the session-projection
164
- * pipeline. `ok` is always `true` here (a delivered projection is by
165
- * definition available); it is kept for wire compatibility with the
166
- * snapshot shape.
141
+ * The `contextTimeline` projection's whole value — the same snapshot the Client has always rendered. `ok` is always `true` here (a
142
+ * delivered projection is by definition available); kept for wire compatibility with the snapshot shape.
167
143
  */
168
144
  type ContextTimeline = Snapshot;
169
145
  /**
@@ -172,20 +148,14 @@ type ContextTimeline = Snapshot;
172
148
  * session log, immune to the request/event retention bounds).
173
149
  */
174
150
  interface CostBucketTotals {
175
- /** Billed prompt tokens that missed the provider cache. */
176
151
  uncached: number;
177
- /** Billed prompt tokens served from the provider cache. */
178
152
  cacheRead: number;
179
- /** Billed prompt tokens written into the provider cache. */
180
153
  cacheWrite: number;
181
- /** Billed output tokens (reasoning included). */
182
154
  output: number;
183
155
  }
184
156
  /** One model family's totals split by DeepSeek's pricing period (Beijing Time). */
185
157
  interface CostFamilyUsage {
186
- /** Peak windows: 09:00-12:00 and 14:00-18:00 Beijing Time, weekdays only. */
187
158
  peak?: CostBucketTotals;
188
- /** All other hours plus all of Saturday/Sunday (half the peak rate). */
189
159
  off?: CostBucketTotals;
190
160
  }
191
161
  /**
@@ -202,7 +172,6 @@ interface SessionCostUsage {
202
172
  /** One model-visible message on the surface, with its heuristic token price. */
203
173
  interface SurfaceNode {
204
174
  seq: number;
205
- /** Event timestamp (ms epoch); the Client shows it when present. */
206
175
  time?: number;
207
176
  cat: Category;
208
177
  tokens: number;
@@ -236,6 +205,12 @@ interface RequestRecord {
236
205
  tool: number;
237
206
  total: number;
238
207
  prompt?: number;
208
+ /**
209
+ * Billed cache-read (served) prompt tokens of this request — the
210
+ * hit-rate numerator against `prompt` (input + cacheRead + cacheWrite).
211
+ * Absent on older hosts / usage-less requests; zero is a real value.
212
+ */
213
+ cacheRead?: number;
239
214
  output?: number;
240
215
  /**
241
216
  * Turn-mode aggregate marker, set by the Client's aggregateByTurn (one bar
@@ -280,10 +255,7 @@ interface HeaderTool {
280
255
  schema?: unknown;
281
256
  }
282
257
  /**
283
- * One request-header epoch: the full system prompt and tool schemas in force
284
- * from this event's seq until the next epoch. Headers change rarely (the loop
285
- * only logs `request/header` on change), so this unit's pushes are rare and
286
- * carrying full content is cheap.
258
+ * One request-header epoch: the full system prompt and tool schemas in force from this event's seq until the next epoch.
287
259
  */
288
260
  interface HeaderRecord {
289
261
  seq: number;
@@ -307,11 +279,9 @@ interface ContextHeaders {
307
279
  * (not only when the raw step bound is), so the bounded state stays at the
308
280
  * newest ~`maxKeptTurns` turns deterministically as a live log grows.
309
281
  */
310
- /** The projection unit's persisted state (plain JSON, bounded see above). */
311
282
  interface TimelineState {
312
283
  /** Model-visible surface, newest last. */
313
284
  surface: SurfaceNode[];
314
- /** Live per-category token sums over the surface. */
315
285
  sums: Record<Category, number>;
316
286
  systemTokens: number;
317
287
  toolsTokens: number;
@@ -351,7 +321,6 @@ interface TimelineState {
351
321
  * Absent until a v4-flash / v4-pro request reports usage.
352
322
  */
353
323
  cost?: SessionCostUsage;
354
- /** Newest `gone` among archive entries dropped by the retention bounds. */
355
324
  archiveFloor?: number;
356
325
  /**
357
326
  * Tool callId → name, armed by `tool/call` and DELETED when its
@@ -608,14 +577,12 @@ declare class SessionProjectionRegistry extends Service {
608
577
  }
609
578
  //#endregion
610
579
  //#region src/host/headers.d.ts
611
- /** The unit's persisted state (plain JSON, bounded). */
612
580
  interface HeadersState {
613
581
  headers: HeaderRecord[];
614
582
  }
615
583
  //#endregion
616
584
  //#region src/host/index.d.ts
617
585
  declare const name = "dsh-context";
618
- /** Required services: the session-projection registry that drives the unit. */
619
586
  declare const inject: string[];
620
587
  declare function apply(ctx: Context, config: Config): void;
621
588
  //#endregion
package/lib/index.js CHANGED
@@ -17,7 +17,6 @@ import { deriveEventMessage } from "@deepseek-ai/dsh-session";
17
17
  * only tune the fold's retention / presentation slice, so changing them never
18
18
  * requires a projection `stateVersion` bump.
19
19
  */
20
- /** Defaults — the exact bounds the fold used before they became configurable. */
21
20
  const DEFAULT_BOUNDS = {
22
21
  maxRequestSteps: 1500,
23
22
  maxKeptTurns: 300,
@@ -26,8 +25,8 @@ const DEFAULT_BOUNDS = {
26
25
  maxArchiveNodes: 400
27
26
  };
28
27
  /**
29
- * The cordis `Config` validator: strict on keys, defaults on the schema fields.
30
- * Tolerates `undefined` (a patch row without a `config:` block — defaults win).
28
+ * The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
29
+ * block — defaults win).
31
30
  */
32
31
  const Config = z.preprocess((v) => v ?? {}, z.object({
33
32
  maxRequestSteps: z.number().int().min(1).default(DEFAULT_BOUNDS.maxRequestSteps),
@@ -36,7 +35,6 @@ const Config = z.preprocess((v) => v ?? {}, z.object({
36
35
  maxNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxNodes),
37
36
  maxArchiveNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxArchiveNodes)
38
37
  }).strict());
39
- /** Validate (and default) the entry config into concrete fold bounds. */
40
38
  function resolveBounds(config) {
41
39
  return Config.parse(config ?? {});
42
40
  }
@@ -69,7 +67,6 @@ const MAX_WH_RATIO = 8;
69
67
  const MIN_PIXELS = 147456;
70
68
  const floorDiv = (a, b) => Math.floor(a / b);
71
69
  const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
72
- /** Token count of one patch grid (rows×cols) under the v4 layout rule. */
73
70
  function gridTokens(rows, cols) {
74
71
  let n = rows * (cols + 1) + 2;
75
72
  if (rows % 2 === 1) n += cols + 1;
@@ -151,10 +148,8 @@ function calcResizeInner(width, height) {
151
148
  return safeResize(h, w, paddedHeight, paddedWidth);
152
149
  }
153
150
  /**
154
- * Estimate the tokens one image consumes in a DeepSeek vision request, from
155
- * its pixel dimensions. Returns null for non-positive/non-finite dimensions
156
- * or when the official iteration fails to converge — callers fall back to
157
- * the generic structural price.
151
+ * Estimate the tokens one image consumes in a DeepSeek vision request from its pixel dimensions. Returns null for non-positive/non-finite
152
+ * dimensions or when the official iteration fails to converge — callers fall back to the generic structural price.
158
153
  */
159
154
  function estimateImageTokens(width, height) {
160
155
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
@@ -188,7 +183,6 @@ function estimateImageTokens(width, height) {
188
183
  const CHARS_PER_TOKEN = 4;
189
184
  const BLOCK_OVERHEAD = 4;
190
185
  const ROLE_OVERHEAD = 4;
191
- /** Whole-array tool-schema price (the header's tools total). */
192
186
  function estimateToolsTotal(tools) {
193
187
  return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
194
188
  }
@@ -234,9 +228,8 @@ function estimateToolSchema(tool) {
234
228
  return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
235
229
  }
236
230
  /**
237
- * Count image blocks in a message payload, recursing into nested content
238
- * (tool-result blocks carry their inner blocks) feeds the stats board's
239
- * whole-session image-file cell.
231
+ * Count image blocks in a message payload, recursing into nested content (tool-result blocks carry their inner blocks) — seeds each node's
232
+ * `imgs`, which the stats board's image cell sums over the LIVE surface (compacted/pruned messages stop counting).
240
233
  */
241
234
  function imageCountOf(blocks) {
242
235
  let count = 0;
@@ -312,12 +305,10 @@ const contextHeadersSchema = z.object({ headers: z.array(z.object({
312
305
  tools: z.array(headerToolSchema)
313
306
  }).strict()) }).strict();
314
307
  /**
315
- * The persisted state and the wire view are the same shape here (the view
316
- * only shallow-copies each record), so one schema validates both under the
317
- * dsh 0.1.1-rc.1+ `stateSchema`/`wire` contract.
308
+ * State and wire are the same shape (the view only shallow-copies each record), so one schema validates both under the dsh 0.1.1-rc.1+
309
+ * `stateSchema`/`wire` contract.
318
310
  */
319
311
  const contextHeadersStateSchema = contextHeadersSchema;
320
- /** Fold one `request/header` payload into an epoch record (display-priced). */
321
312
  function recordOf(event) {
322
313
  if (event.type !== "request/header") return null;
323
314
  const rawHeader = event.data.header;
@@ -342,14 +333,9 @@ function recordOf(event) {
342
333
  return record;
343
334
  }
344
335
  /**
345
- * The context-headers projection unit. Registered alongside the timeline
346
- * unit (host/index.ts); clients read it through `useProjection('contextHeaders')`
347
- * and degrade to tokens-only header sections when the key is absent.
348
- *
349
- * Dual-contract definition (see compat.ts): `schema`/`view` for
350
- * dsh <= 0.1.0-rc.8, `stateSchema`/`wire` for dsh >= 0.1.1-rc.1. Without
351
- * `wire`, the 0.1.1-rc.1+ registry treats the unit as host-only and the
352
- * Context browser's system/tools sections would degrade.
336
+ * The context-headers projection unit; registered alongside the timeline unit (host/index.ts); clients read it through
337
+ * `useProjection('contextHeaders')` and degrade to tokens-only header sections when the key is absent. Dual-contract definition (see
338
+ * compat.ts).
353
339
  */
354
340
  function createContextHeadersDefinition() {
355
341
  const view = (state) => ({ headers: state.headers.map((h) => ({
@@ -394,7 +380,6 @@ function installSettings(ctx) {
394
380
  }
395
381
  //#endregion
396
382
  //#region src/host/fold.ts
397
- /** Keep only the trailing `maxTurns` turn-runs of a request timeline. */
398
383
  function trimToLastTurns(requests, maxTurns) {
399
384
  let runs = 0;
400
385
  let start = requests.length;
@@ -410,7 +395,6 @@ function trimToLastTurns(requests, maxTurns) {
410
395
  }
411
396
  return requests.slice(start);
412
397
  }
413
- /** Distinct turn runs in a request timeline (consecutive equal-turn runs). */
414
398
  function countTurnRuns(requests) {
415
399
  let runs = 0;
416
400
  let prevTurn;
@@ -420,7 +404,6 @@ function countTurnRuns(requests) {
420
404
  }
421
405
  return runs;
422
406
  }
423
- /** Retain the newest tail of the two unbounded lists (bounded persisted state). */
424
407
  function trimState(st, bounds) {
425
408
  if (countTurnRuns(st.requests) > bounds.maxKeptTurns) st.requests = trimToLastTurns(st.requests, bounds.maxKeptTurns);
426
409
  if (st.requests.length > bounds.maxRequestSteps) st.requests = st.requests.slice(-bounds.maxRequestSteps);
@@ -767,6 +750,7 @@ function applyTimeline(state, event, bounds) {
767
750
  if (data && typeof data.step === "number") record.step = data.step;
768
751
  if (usage && typeof usage.inputTokens === "number") {
769
752
  record.prompt = usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
753
+ if (typeof usage.cacheReadTokens === "number") record.cacheRead = usage.cacheReadTokens;
770
754
  if (typeof usage.outputTokens === "number") record.output = usage.outputTokens;
771
755
  accumulateCost(s, event.time, usage);
772
756
  }
@@ -808,10 +792,9 @@ function applyTimeline(state, event, bounds) {
808
792
  return state;
809
793
  }
810
794
  /**
811
- * Build the wire snapshot served to the browser the projection's `view()`.
812
- * Bounds the surface nodes (newest carry the signal), and attributes each
813
- * event to the request around it by stamping COPIES (the persisted state
814
- * objects are never mutated).
795
+ * Serve the projection's wire view: bound the surface nodes to the newest tail and attach each event to the request around it; stamp
796
+ * COPIES
797
+ * the persisted state objects are never mutated.
815
798
  */
816
799
  function buildTimelineView(state, bounds) {
817
800
  const surfaceTotal = state.sums.user + state.sums.inject + state.sums.assistant + state.sums.tool;
@@ -936,6 +919,7 @@ const requestRecordSchema = z.object({
936
919
  tool: z.number().int().nonnegative(),
937
920
  total: z.number().int().nonnegative(),
938
921
  prompt: z.number().int().nonnegative().optional(),
922
+ cacheRead: z.number().int().nonnegative().optional(),
939
923
  output: z.number().int().nonnegative().optional(),
940
924
  stepCount: z.number().int().positive().optional()
941
925
  }).strict();
@@ -1072,13 +1056,12 @@ function createContextTimelineDefinition(config) {
1072
1056
  },
1073
1057
  init: () => createTimelineState(),
1074
1058
  apply: (state, event) => applyTimeline(state, event, bounds),
1075
- stateVersion: 8
1059
+ stateVersion: 9
1076
1060
  };
1077
1061
  }
1078
1062
  //#endregion
1079
1063
  //#region src/host/index.ts
1080
1064
  const name = "dsh-context";
1081
- /** Required services: the session-projection registry that drives the unit. */
1082
1065
  const inject = ["sessionProjections"];
1083
1066
  function apply(ctx, config) {
1084
1067
  ctx.sessionProjections.register(createContextTimelineDefinition(config));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.29.0",
3
+ "version": "0.30.1",
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
  "author": "bowenliang123",
6
6
  "repository": {