dsh-context 0.28.0 → 0.30.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
@@ -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;
@@ -280,10 +249,7 @@ interface HeaderTool {
280
249
  schema?: unknown;
281
250
  }
282
251
  /**
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.
252
+ * One request-header epoch: the full system prompt and tool schemas in force from this event's seq until the next epoch.
287
253
  */
288
254
  interface HeaderRecord {
289
255
  seq: number;
@@ -307,11 +273,9 @@ interface ContextHeaders {
307
273
  * (not only when the raw step bound is), so the bounded state stays at the
308
274
  * newest ~`maxKeptTurns` turns deterministically as a live log grows.
309
275
  */
310
- /** The projection unit's persisted state (plain JSON, bounded see above). */
311
276
  interface TimelineState {
312
277
  /** Model-visible surface, newest last. */
313
278
  surface: SurfaceNode[];
314
- /** Live per-category token sums over the surface. */
315
279
  sums: Record<Category, number>;
316
280
  systemTokens: number;
317
281
  toolsTokens: number;
@@ -351,7 +315,6 @@ interface TimelineState {
351
315
  * Absent until a v4-flash / v4-pro request reports usage.
352
316
  */
353
317
  cost?: SessionCostUsage;
354
- /** Newest `gone` among archive entries dropped by the retention bounds. */
355
318
  archiveFloor?: number;
356
319
  /**
357
320
  * Tool callId → name, armed by `tool/call` and DELETED when its
@@ -608,14 +571,12 @@ declare class SessionProjectionRegistry extends Service {
608
571
  }
609
572
  //#endregion
610
573
  //#region src/host/headers.d.ts
611
- /** The unit's persisted state (plain JSON, bounded). */
612
574
  interface HeadersState {
613
575
  headers: HeaderRecord[];
614
576
  }
615
577
  //#endregion
616
578
  //#region src/host/index.d.ts
617
579
  declare const name = "dsh-context";
618
- /** Required services: the session-projection registry that drives the unit. */
619
580
  declare const inject: string[];
620
581
  declare function apply(ctx: Context, config: Config): void;
621
582
  //#endregion
package/lib/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { z } from "zod";
2
+ import { settingsNamespace } from "@deepseek-ai/dsh-settings";
3
+ import z$1 from "@deepseek-ai/schemastery";
2
4
  import { deriveEventMessage } from "@deepseek-ai/dsh-session";
3
5
  //#region src/host/config.ts
4
6
  /**
@@ -15,7 +17,6 @@ import { deriveEventMessage } from "@deepseek-ai/dsh-session";
15
17
  * only tune the fold's retention / presentation slice, so changing them never
16
18
  * requires a projection `stateVersion` bump.
17
19
  */
18
- /** Defaults — the exact bounds the fold used before they became configurable. */
19
20
  const DEFAULT_BOUNDS = {
20
21
  maxRequestSteps: 1500,
21
22
  maxKeptTurns: 300,
@@ -24,8 +25,8 @@ const DEFAULT_BOUNDS = {
24
25
  maxArchiveNodes: 400
25
26
  };
26
27
  /**
27
- * The cordis `Config` validator: strict on keys, defaults on the schema fields.
28
- * 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).
29
30
  */
30
31
  const Config = z.preprocess((v) => v ?? {}, z.object({
31
32
  maxRequestSteps: z.number().int().min(1).default(DEFAULT_BOUNDS.maxRequestSteps),
@@ -34,7 +35,6 @@ const Config = z.preprocess((v) => v ?? {}, z.object({
34
35
  maxNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxNodes),
35
36
  maxArchiveNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxArchiveNodes)
36
37
  }).strict());
37
- /** Validate (and default) the entry config into concrete fold bounds. */
38
38
  function resolveBounds(config) {
39
39
  return Config.parse(config ?? {});
40
40
  }
@@ -67,7 +67,6 @@ const MAX_WH_RATIO = 8;
67
67
  const MIN_PIXELS = 147456;
68
68
  const floorDiv = (a, b) => Math.floor(a / b);
69
69
  const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
70
- /** Token count of one patch grid (rows×cols) under the v4 layout rule. */
71
70
  function gridTokens(rows, cols) {
72
71
  let n = rows * (cols + 1) + 2;
73
72
  if (rows % 2 === 1) n += cols + 1;
@@ -149,10 +148,8 @@ function calcResizeInner(width, height) {
149
148
  return safeResize(h, w, paddedHeight, paddedWidth);
150
149
  }
151
150
  /**
152
- * Estimate the tokens one image consumes in a DeepSeek vision request, from
153
- * its pixel dimensions. Returns null for non-positive/non-finite dimensions
154
- * or when the official iteration fails to converge — callers fall back to
155
- * 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.
156
153
  */
157
154
  function estimateImageTokens(width, height) {
158
155
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
@@ -186,7 +183,6 @@ function estimateImageTokens(width, height) {
186
183
  const CHARS_PER_TOKEN = 4;
187
184
  const BLOCK_OVERHEAD = 4;
188
185
  const ROLE_OVERHEAD = 4;
189
- /** Whole-array tool-schema price (the header's tools total). */
190
186
  function estimateToolsTotal(tools) {
191
187
  return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
192
188
  }
@@ -232,9 +228,8 @@ function estimateToolSchema(tool) {
232
228
  return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
233
229
  }
234
230
  /**
235
- * Count image blocks in a message payload, recursing into nested content
236
- * (tool-result blocks carry their inner blocks) feeds the stats board's
237
- * 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).
238
233
  */
239
234
  function imageCountOf(blocks) {
240
235
  let count = 0;
@@ -310,12 +305,10 @@ const contextHeadersSchema = z.object({ headers: z.array(z.object({
310
305
  tools: z.array(headerToolSchema)
311
306
  }).strict()) }).strict();
312
307
  /**
313
- * The persisted state and the wire view are the same shape here (the view
314
- * only shallow-copies each record), so one schema validates both under the
315
- * 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.
316
310
  */
317
311
  const contextHeadersStateSchema = contextHeadersSchema;
318
- /** Fold one `request/header` payload into an epoch record (display-priced). */
319
312
  function recordOf(event) {
320
313
  if (event.type !== "request/header") return null;
321
314
  const rawHeader = event.data.header;
@@ -340,14 +333,9 @@ function recordOf(event) {
340
333
  return record;
341
334
  }
342
335
  /**
343
- * The context-headers projection unit. Registered alongside the timeline
344
- * unit (host/index.ts); clients read it through `useProjection('contextHeaders')`
345
- * and degrade to tokens-only header sections when the key is absent.
346
- *
347
- * Dual-contract definition (see compat.ts): `schema`/`view` for
348
- * dsh <= 0.1.0-rc.8, `stateSchema`/`wire` for dsh >= 0.1.1-rc.1. Without
349
- * `wire`, the 0.1.1-rc.1+ registry treats the unit as host-only and the
350
- * 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).
351
339
  */
352
340
  function createContextHeadersDefinition() {
353
341
  const view = (state) => ({ headers: state.headers.map((h) => ({
@@ -376,8 +364,22 @@ function createContextHeadersDefinition() {
376
364
  };
377
365
  }
378
366
  //#endregion
367
+ //#region src/host/settings.ts
368
+ /** The namespace is the join key between the Host registration and the browser card. */
369
+ const SETTINGS_NAMESPACE = "dsh-context";
370
+ /** Section schema: also the wire envelope the browser scope validates against. */
371
+ const SettingsSchema = z$1.object({
372
+ defaultGranularity: z$1.union(["step", "turn"]).default("step"),
373
+ defaultTrendMode: z$1.union(["total", "diff"]).default("total")
374
+ });
375
+ /** Serve the namespace while a settings provider is composed; inert otherwise. */
376
+ function installSettings(ctx) {
377
+ ctx.inject(["settings"], (sctx) => {
378
+ sctx.settings.register(settingsNamespace(SETTINGS_NAMESPACE), SettingsSchema);
379
+ });
380
+ }
381
+ //#endregion
379
382
  //#region src/host/fold.ts
380
- /** Keep only the trailing `maxTurns` turn-runs of a request timeline. */
381
383
  function trimToLastTurns(requests, maxTurns) {
382
384
  let runs = 0;
383
385
  let start = requests.length;
@@ -393,7 +395,6 @@ function trimToLastTurns(requests, maxTurns) {
393
395
  }
394
396
  return requests.slice(start);
395
397
  }
396
- /** Distinct turn runs in a request timeline (consecutive equal-turn runs). */
397
398
  function countTurnRuns(requests) {
398
399
  let runs = 0;
399
400
  let prevTurn;
@@ -403,7 +404,6 @@ function countTurnRuns(requests) {
403
404
  }
404
405
  return runs;
405
406
  }
406
- /** Retain the newest tail of the two unbounded lists (bounded persisted state). */
407
407
  function trimState(st, bounds) {
408
408
  if (countTurnRuns(st.requests) > bounds.maxKeptTurns) st.requests = trimToLastTurns(st.requests, bounds.maxKeptTurns);
409
409
  if (st.requests.length > bounds.maxRequestSteps) st.requests = st.requests.slice(-bounds.maxRequestSteps);
@@ -791,10 +791,9 @@ function applyTimeline(state, event, bounds) {
791
791
  return state;
792
792
  }
793
793
  /**
794
- * Build the wire snapshot served to the browser the projection's `view()`.
795
- * Bounds the surface nodes (newest carry the signal), and attributes each
796
- * event to the request around it by stamping COPIES (the persisted state
797
- * objects are never mutated).
794
+ * Serve the projection's wire view: bound the surface nodes to the newest tail and attach each event to the request around it; stamp
795
+ * COPIES
796
+ * the persisted state objects are never mutated.
798
797
  */
799
798
  function buildTimelineView(state, bounds) {
800
799
  const surfaceTotal = state.sums.user + state.sums.inject + state.sums.assistant + state.sums.tool;
@@ -1061,11 +1060,11 @@ function createContextTimelineDefinition(config) {
1061
1060
  //#endregion
1062
1061
  //#region src/host/index.ts
1063
1062
  const name = "dsh-context";
1064
- /** Required services: the session-projection registry that drives the unit. */
1065
1063
  const inject = ["sessionProjections"];
1066
1064
  function apply(ctx, config) {
1067
1065
  ctx.sessionProjections.register(createContextTimelineDefinition(config));
1068
1066
  ctx.sessionProjections.register(createContextHeadersDefinition());
1067
+ installSettings(ctx);
1069
1068
  }
1070
1069
  //#endregion
1071
1070
  export { Config, apply, inject, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.28.0",
3
+ "version": "0.30.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
  "author": "bowenliang123",
6
6
  "repository": {
@@ -51,7 +51,8 @@
51
51
  "@deepseek-ai/dsh-client-connection",
52
52
  "@deepseek-ai/dsh-client-locale",
53
53
  "@deepseek-ai/dsh-client-runtime",
54
- "@deepseek-ai/dsh-client-ui-conversation"
54
+ "@deepseek-ai/dsh-client-ui-conversation",
55
+ "@deepseek-ai/dsh-client-ui-settings"
55
56
  ],
56
57
  "platform": "web"
57
58
  }
@@ -70,6 +71,8 @@
70
71
  "@deepseek-ai/cordis": "^4.0.1",
71
72
  "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
72
73
  "@deepseek-ai/dsh-session": "^0.1.0-rc.7",
74
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
75
+ "@deepseek-ai/schemastery": "^3.18.1",
73
76
  "react": "^18.3.1",
74
77
  "zod": "^4.4.3"
75
78
  },
@@ -86,6 +89,8 @@
86
89
  "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
87
90
  "@deepseek-ai/dsh-session": "^0.1.0-rc.7",
88
91
  "@deepseek-ai/dsh-session-projection": "^0.1.0-rc.7",
92
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
93
+ "@deepseek-ai/schemastery": "^3.18.1",
89
94
  "@stylistic/eslint-plugin": "^5.10.0",
90
95
  "@types/react": "^18.3.31",
91
96
  "eslint-plugin-sonarjs": "^4.2.0",