dsh-context 0.49.4 → 0.51.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,7 +2,7 @@ import { z } from "zod";
2
2
  import "@deepseek-ai/dsh-session";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
  //#region src/host/config.d.ts
5
- interface Config {
5
+ export interface Config {
6
6
  /** Cap on kept per-step request records (the hard step backstop). */
7
7
  maxRequestSteps?: number;
8
8
  /** Newest whole-turn window kept; trimming crosses whole turns, never mid-turn. */
@@ -23,7 +23,7 @@ interface Config {
23
23
  * The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
24
24
  * block — defaults win).
25
25
  */
26
- declare const Config: z.ZodPreprocess<z.ZodObject<{
26
+ export declare const Config: z.ZodPreprocess<z.ZodObject<{
27
27
  maxRequestSteps: z.ZodDefault<z.ZodNumber>;
28
28
  maxKeptTurns: z.ZodDefault<z.ZodNumber>;
29
29
  maxEvents: z.ZodDefault<z.ZodNumber>;
@@ -128,13 +128,21 @@ interface TimelineState {
128
128
  */
129
129
  archived: SurfaceNode[];
130
130
  /**
131
- * Session-cost raw material: cumulative billed-token totals per DeepSeek
132
- * model family and pricing period (see SessionCostUsage). Running
133
- * totals — never trimmed, so the estimate always covers the COMPLETE
134
- * session log even after the request/event retention bounds cut in.
135
- * Absent until a DeepSeek flash/pro request reports usage.
131
+ * Session-cost raw material: cumulative billed-token totals per
132
+ * (provider, model), split into pricing periods for DeepSeek (see
133
+ * SessionCostUsage / CostModelUsage). Running totals — never trimmed, so
134
+ * the estimate always covers the COMPLETE session log even after the
135
+ * request/event retention bounds cut in. Absent until a usage-reporting
136
+ * request with a known model folds.
136
137
  */
137
138
  cost?: SessionCostUsage;
139
+ /**
140
+ * Whole-session human-input tally (see Snapshot.humanInputs): every
141
+ * non-injection `user/message` plus every answered `ask_user_question`
142
+ * result. Running total — never trimmed, like `cost`/`timing`. Absent until
143
+ * the first human input folds.
144
+ */
145
+ humanInputs?: number;
138
146
  archiveFloor?: number;
139
147
  /**
140
148
  * The detail collections' revision marker (see ContextTimelineDetail):
@@ -339,6 +347,14 @@ interface Snapshot {
339
347
  * hosts; clients treat absence as zero.
340
348
  */
341
349
  toolCalls?: number;
350
+ /**
351
+ * Whole-session human-input tally: every non-injection `user/message`
352
+ * (the user's own messages) plus every answered `ask_user_question`
353
+ * result (one per answer submission). A running total over the COMPLETE
354
+ * log — turns the retained window no longer holds still count. Absent
355
+ * from older hosts; clients treat absence as zero.
356
+ */
357
+ humanInputs?: number;
342
358
  /**
343
359
  * Split-generation head fields — present exactly when the host serves the
344
360
  * SLIM head (the heavy collections moved to the on-demand detail channel,
@@ -359,9 +375,9 @@ interface Snapshot {
359
375
  requests: RequestRecord[];
360
376
  events: ContextEventRecord[];
361
377
  /**
362
- * Cumulative session-cost raw material (per-family, per-period billed
363
- * token totals — see SessionCostUsage). Absent until a DeepSeek V4
364
- * request reports usage.
378
+ * Cumulative session-cost raw material (per-provider, per-model billed
379
+ * token totals — see SessionCostUsage). Absent until a request with a
380
+ * known model reports usage.
365
381
  */
366
382
  cost?: SessionCostUsage;
367
383
  /**
@@ -558,22 +574,28 @@ interface TimingTotals {
558
574
  /** Per-tool-name tallies behind the timing card's ranking (bounded). */
559
575
  tools: Record<string, ToolTimingTotals>;
560
576
  }
561
- /** One model family's totals split by DeepSeek's pricing period (Beijing Time). */
562
- interface CostFamilyUsage {
577
+ /**
578
+ * One billed model's cumulative totals split by pricing period. Providers
579
+ * without period-based pricing book everything under `peak` (the list-price
580
+ * period); DeepSeek splits at fold time — peak windows bill at list price,
581
+ * off-peak (all other hours) at half.
582
+ */
583
+ interface CostModelUsage {
563
584
  peak?: CostBucketTotals;
564
585
  off?: CostBucketTotals;
565
586
  }
566
587
  /**
567
588
  * The session-cost estimate's raw material: cumulative provider-reported
568
- * token totals per DeepSeek model family (matched on the model NAME,
569
- * provider-agnostic) and pricing period. The Client prices these with its
570
- * hardcoded list-price table in the locale's currency. Absent until a
571
- * deepseek-flash / deepseek-v4.1-flash / deepseek-v4-pro request reports
572
- * usage.
589
+ * billed-token totals, keyed by the request envelope's DSH provider id (''
590
+ * when a log carries none) and then by its model id — the exact (provider,
591
+ * model) faces the Client's model-price book resolves (the models.dev
592
+ * registry, client/modelPrices.ts). Running totals per key; absent until a
593
+ * request with a known model reports usage.
573
594
  */
574
595
  interface SessionCostUsage {
575
- flash?: CostFamilyUsage;
576
- pro?: CostFamilyUsage;
596
+ [provider: string]: {
597
+ [model: string]: CostModelUsage;
598
+ };
577
599
  }
578
600
  /** One model-visible message on the surface, with its heuristic token price. */
579
601
  interface SurfaceNode {
@@ -688,8 +710,8 @@ interface ContextHeaders {
688
710
  }
689
711
  //#endregion
690
712
  //#region src/host/index.d.ts
691
- declare const name = "dsh-context";
692
- declare const inject: string[];
693
- declare function apply(ctx: Context, config: Config): void;
713
+ export declare const name = "dsh-context";
714
+ export declare const inject: string[];
715
+ export declare function apply(ctx: Context, config: Config): void;
694
716
  //#endregion
695
- export { type Category, Config, type ContextEventRecord, type ContextHeaders, type ContextTimeline, type ContextTimelineDetail, type HeaderRecord, type HeaderTool, type HeadersState, type RequestRecord, type Snapshot, type SurfaceNode, type TimelineCounts, type TimelineLast, type TimelineState, apply, inject, name };
717
+ export type { Category, ContextEventRecord, ContextHeaders, ContextTimeline, ContextTimelineDetail, HeaderRecord, HeaderTool, HeadersState, RequestRecord, Snapshot, SurfaceNode, TimelineCounts, TimelineLast, TimelineState };
package/lib/index.js CHANGED
@@ -293,6 +293,29 @@ function resolveBounds(config) {
293
293
  return Config.parse(config ?? {});
294
294
  }
295
295
  //#endregion
296
+ //#region src/shared/providers.ts
297
+ /**
298
+ * The provider-id seam between dsh request envelopes and the models.dev
299
+ * registry: client/cost.ts resolves price-book branches through it, and
300
+ * host/fold.ts uses the DeepSeek resolution to split the session-cost totals
301
+ * into peak/off-peak periods. Only the renames live here — an id absent
302
+ * from the table passes through verbatim.
303
+ */
304
+ const MODELS_DEV_PROVIDER_IDS = {
305
+ "deepseek-official": "deepseek",
306
+ "kimi-coding": "moonshotai",
307
+ "minimax-cn": "minimax",
308
+ "zai-coding-cn": "zhipuai"
309
+ };
310
+ /** The models.dev provider id that prices a dsh provider (identity for unmapped ids). */
311
+ function modelsDevProviderOf(dshProviderId) {
312
+ return MODELS_DEV_PROVIDER_IDS[dshProviderId] ?? dshProviderId;
313
+ }
314
+ /** Whether a dsh provider prices through DeepSeek's period-based list (peak / half-price off-peak). */
315
+ function isDeepSeekProvider(dshProviderId) {
316
+ return modelsDevProviderOf(dshProviderId) === "deepseek";
317
+ }
318
+ //#endregion
296
319
  //#region src/shared/estimate.ts
297
320
  /**
298
321
  * Token heuristics shared by the host fold and the client boundary — the
@@ -1274,31 +1297,16 @@ function tokenCountOf(value) {
1274
1297
  return null;
1275
1298
  }
1276
1299
  /**
1277
- * The DeepSeek model family a model name prices as — matched on the NAME
1278
- * alone (provider-agnostic: official API, proxies, OpenRouter spellings like
1279
- * `deepseek/deepseek-v4.1-flash` and `deepseek/deepseek-flash` all land
1280
- * here). The name must carry a DeepSeek marker (`v4` or `deepseek`) so a
1281
- * foreign flash/pro-named model (gemini-2.0-flash) is never priced.
1282
- */
1283
- function costFamilyOf(model) {
1284
- if (model === void 0) return null;
1285
- const m = model.toLowerCase();
1286
- if (!m.includes("v4") && !m.includes("deepseek")) return null;
1287
- if (m.includes("flash")) return "flash";
1288
- if (m.includes("pro")) return "pro";
1289
- return null;
1290
- }
1291
- /**
1292
- * DeepSeek's peak windows (Beijing Time, UTC+8): 09:00-12:00 and 14:00-18:00
1293
- * on weekdays; off-peak (half the peak rate) covers all other hours plus all
1294
- * of Saturday and Sunday.
1300
+ * DeepSeek's peak windows (the official list: UTC 01:00–04:00 and 06:00–10:00,
1301
+ * Monday through Friday — Beijing Time 09:00–12:00 and 14:00–18:00). All other
1302
+ * hours, plus entire weekends, bill at the half-price off-peak rate.
1295
1303
  */
1296
1304
  function isPeakUtc(time) {
1297
- const bj = new Date(time + 288e5);
1298
- const day = bj.getUTCDay();
1305
+ const at = new Date(time);
1306
+ const day = at.getUTCDay();
1299
1307
  if (day === 0 || day === 6) return false;
1300
- const h = bj.getUTCHours();
1301
- return h >= 9 && h < 12 || h >= 14 && h < 18;
1308
+ const h = at.getUTCHours();
1309
+ return h >= 1 && h < 4 || h >= 6 && h < 10;
1302
1310
  }
1303
1311
  /**
1304
1312
  * Fold one billed request into the session-cost totals, cloning along the
@@ -1306,29 +1314,42 @@ function isPeakUtc(time) {
1306
1314
  * previous state — the apply contract never mutates it in place). The
1307
1315
  * buckets arrive sanitized ({@link BilledUsage}), so the totals stay at the
1308
1316
  * schemas' non-negative safe integers no matter what the provider reported.
1317
+ * The key is the request envelope's (provider, model) face — the exact
1318
+ * lookup the Client's model-price book resolves (models.dev). A request
1319
+ * without a provider still accumulates (under the '' key) and the Client
1320
+ * prices it when the model id is unambiguous; without a model there is
1321
+ * nothing to price. DeepSeek's period-based list splits the buckets
1322
+ * (peak windows at list price, all other hours half price); every other
1323
+ * provider books everything under the list-price period.
1309
1324
  */
1310
1325
  function accumulateCost(st, time, usage) {
1311
- const family = costFamilyOf(st.model);
1312
- if (family === null) return;
1313
- const prev = st.cost ?? {};
1314
- const fam = prev[family] ?? {};
1315
- const period = isPeakUtc(time) ? "peak" : "off";
1316
- const b = fam[period] ?? {
1326
+ const model = st.model;
1327
+ if (model === void 0) return;
1328
+ const provider = st.provider ?? "";
1329
+ const period = isDeepSeekProvider(provider) && !isPeakUtc(time) ? "off" : "peak";
1330
+ const models = st.cost?.[provider] ?? {};
1331
+ const periods = models[model] ?? {};
1332
+ const b = periods[period] ?? {
1317
1333
  uncached: 0,
1318
1334
  cacheRead: 0,
1319
1335
  cacheWrite: 0,
1320
1336
  output: 0
1321
1337
  };
1322
- const nextFam = { ...fam };
1323
- nextFam[period] = {
1338
+ const nextPeriods = { ...periods };
1339
+ nextPeriods[period] = {
1324
1340
  uncached: b.uncached + usage.input,
1325
1341
  cacheRead: b.cacheRead + usage.cacheRead,
1326
1342
  cacheWrite: b.cacheWrite + usage.cacheWrite,
1327
1343
  output: b.output + usage.output
1328
1344
  };
1329
- const next = { ...prev };
1330
- next[family] = nextFam;
1331
- st.cost = next;
1345
+ const nextModels = {
1346
+ ...models,
1347
+ [model]: nextPeriods
1348
+ };
1349
+ st.cost = {
1350
+ ...st.cost ?? {},
1351
+ [provider]: nextModels
1352
+ };
1332
1353
  }
1333
1354
  /**
1334
1355
  * Advance the fold over ONE committed session event under the projection
@@ -1340,6 +1361,13 @@ function accumulateCost(st, time, usage) {
1340
1361
  */
1341
1362
  /** The timing card's per-tool ranking cap: the busiest 16 names are kept. */
1342
1363
  const TOOL_TIMING_CAP = 16;
1364
+ /**
1365
+ * The interactive Q&A tool (`dsh-tool-ask-user`): its settled result IS the
1366
+ * user's answer, so it folds into the human-input tally alongside the user's
1367
+ * own messages. One result = one answer submission, however many questions
1368
+ * the prompt carried.
1369
+ */
1370
+ const ASK_USER_TOOL = "ask_user_question";
1343
1371
  /** The decode buckets of the generation split, in card order (see TimingTotals). */
1344
1372
  const DECODE_KINDS = [
1345
1373
  "reasoning",
@@ -1600,7 +1628,7 @@ function applyTimeline(state, event, bounds) {
1600
1628
  if (source.form === "notice" && typeof source.summary === "string" && source.summary !== "") rec.detail = source.summary;
1601
1629
  }
1602
1630
  s.events.push(rec);
1603
- }
1631
+ } else s.humanInputs = (s.humanInputs ?? 0) + 1;
1604
1632
  break;
1605
1633
  }
1606
1634
  case "tool/result": {
@@ -1613,6 +1641,7 @@ function applyTimeline(state, event, bounds) {
1613
1641
  const s = ensure();
1614
1642
  bumpDetailRev(s);
1615
1643
  const node = applySurface(s, event, event.type, data, toolMsg);
1644
+ if (node.tool === ASK_USER_TOOL) s.humanInputs = (s.humanInputs ?? 0) + 1;
1616
1645
  if (pendingEntry !== void 0) pushFileOps(s, opsOfCall({
1617
1646
  seq: event.seq,
1618
1647
  time: event.time,
@@ -1775,6 +1804,7 @@ function headFieldsOf(state) {
1775
1804
  },
1776
1805
  images: state.surface.reduce((n, node) => n + (node.imgs ?? 0), 0),
1777
1806
  toolCalls: state.surface.reduce((n, node) => node.cat === "tool" ? n + 1 : n, 0),
1807
+ humanInputs: state.humanInputs ?? 0,
1778
1808
  requests: [],
1779
1809
  events: [],
1780
1810
  nodes: [],
@@ -1782,18 +1812,18 @@ function headFieldsOf(state) {
1782
1812
  archive: []
1783
1813
  };
1784
1814
  if (state.cost !== void 0) {
1785
- const copyFam = (f) => {
1786
- if (f === void 0) return void 0;
1787
- const out = {};
1788
- if (f.peak !== void 0) out.peak = { ...f.peak };
1789
- if (f.off !== void 0) out.off = { ...f.off };
1790
- return out;
1791
- };
1792
1815
  const cost = {};
1793
- const flash = copyFam(state.cost.flash);
1794
- if (flash !== void 0) cost.flash = flash;
1795
- const pro = copyFam(state.cost.pro);
1796
- if (pro !== void 0) cost.pro = pro;
1816
+ for (const provider in state.cost) {
1817
+ const models = {};
1818
+ for (const model in state.cost[provider]) {
1819
+ const periods = state.cost[provider][model];
1820
+ const copy = {};
1821
+ if (periods.peak !== void 0) copy.peak = { ...periods.peak };
1822
+ if (periods.off !== void 0) copy.off = { ...periods.off };
1823
+ models[model] = copy;
1824
+ }
1825
+ cost[provider] = models;
1826
+ }
1797
1827
  result.cost = cost;
1798
1828
  }
1799
1829
  if (state.timing !== void 0) {
@@ -2331,10 +2361,12 @@ const costBucketsSchema = z.object({
2331
2361
  cacheWrite: z.number().int().nonnegative(),
2332
2362
  output: z.number().int().nonnegative()
2333
2363
  }).strict();
2334
- const costFamilySchema = z.object({
2364
+ const costModelSchema = z.object({
2335
2365
  peak: costBucketsSchema.optional(),
2336
2366
  off: costBucketsSchema.optional()
2337
2367
  }).strict();
2368
+ const costModelsSchema = z.record(z.string(), costModelSchema);
2369
+ const costUsageSchema = z.record(z.string(), costModelsSchema);
2338
2370
  const toolTimingSchema = z.object({
2339
2371
  calls: z.number().int().nonnegative(),
2340
2372
  ms: z.number().nonnegative()
@@ -2387,15 +2419,13 @@ const contextTimelineSchema = z.object({
2387
2419
  current: currentSchema,
2388
2420
  images: z.number().int().nonnegative().optional(),
2389
2421
  toolCalls: z.number().int().nonnegative().optional(),
2422
+ humanInputs: z.number().int().nonnegative().optional(),
2390
2423
  counts: countsSchema.optional(),
2391
2424
  last: lastSchema.optional(),
2392
2425
  detailRev: z.number().int().nonnegative().optional(),
2393
2426
  requests: z.array(requestRecordSchema).optional(),
2394
2427
  events: z.array(contextEventSchema).optional(),
2395
- cost: z.object({
2396
- flash: costFamilySchema.optional(),
2397
- pro: costFamilySchema.optional()
2398
- }).strict().optional(),
2428
+ cost: costUsageSchema.optional(),
2399
2429
  timing: timingTotalsSchema.optional(),
2400
2430
  systems: z.array(systemPromptNodeSchema).optional(),
2401
2431
  nodes: z.array(surfaceNodeSchema).optional(),
@@ -2431,12 +2461,10 @@ const timelineStateSchema = z.object({
2431
2461
  requests: z.array(requestRecordSchema),
2432
2462
  events: z.array(contextEventSchema),
2433
2463
  archived: z.array(surfaceNodeSchema),
2434
- cost: z.object({
2435
- flash: costFamilySchema.optional(),
2436
- pro: costFamilySchema.optional()
2437
- }).strict().optional(),
2464
+ cost: costUsageSchema.optional(),
2438
2465
  archiveFloor: z.number().optional(),
2439
2466
  timing: timingTotalsSchema.optional(),
2467
+ humanInputs: z.number().int().nonnegative().optional(),
2440
2468
  stepStart: z.object({
2441
2469
  time: z.number(),
2442
2470
  firstToken: z.number().optional(),
@@ -2511,7 +2539,7 @@ function createContextTimelineDefinition(config, slim) {
2511
2539
  },
2512
2540
  init: () => createTimelineState(),
2513
2541
  apply: (state, event) => applyTimeline(state, event, bounds),
2514
- stateVersion: 15
2542
+ stateVersion: 18
2515
2543
  };
2516
2544
  }
2517
2545
  //#endregion
@@ -2614,6 +2642,11 @@ const SettingsSchema = z$1.object({
2614
2642
  ]).default("all").loose(),
2615
2643
  defaultGranularity: z$1.union(["step", "turn"]).default("step"),
2616
2644
  defaultTrendMode: z$1.union(["total", "delta"]).default("total").loose(),
2645
+ defaultToolSort: z$1.union([
2646
+ "size",
2647
+ "count",
2648
+ "name"
2649
+ ]).default("count").loose(),
2617
2650
  defaultFileSort: z$1.union([
2618
2651
  "count",
2619
2652
  "latest",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.49.4",
3
+ "version": "0.51.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": {
@@ -107,8 +107,12 @@
107
107
  "@deepseek-ai/dsh-session": "0.1.2-rc.1",
108
108
  "@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
109
109
  "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
110
+ "@deepseek-ai/dsh-util-workspace-path": "0.1.5-rc.1",
110
111
  "@deepseek-ai/schemastery": "^3.18.2",
112
+ "@opencode-ai/models": "^0.0.72",
111
113
  "@stylistic/eslint-plugin": "^5.10.0",
114
+ "@tailwindcss/node": "4.3.3",
115
+ "@tailwindcss/oxide": "4.3.3",
112
116
  "@types/node": "^26.2.0",
113
117
  "@types/react": "^18.3.31",
114
118
  "@types/react-dom": "^18.3.7",
@@ -116,13 +120,14 @@
116
120
  "eslint-plugin-sonarjs": "^4.2.0",
117
121
  "husky": "^9.1.7",
118
122
  "jsdom": "^30.0.1",
119
- "lightningcss": "^1.33.0",
120
- "oxlint": "1.76.0",
123
+ "lightningcss": "1.32.0",
124
+ "oxlint": "1.82.0",
121
125
  "oxlint-tsgolint": "7.0.2001",
122
126
  "playwright-core": "^1.62.1",
123
127
  "react": "^18.3.1",
124
128
  "react-dom": "^18.3.1",
125
- "tsdown": "^0.22.14",
129
+ "tailwindcss": "4.3.3",
130
+ "tsdown": "^0.23.0",
126
131
  "typescript": "^7.0.2",
127
132
  "vitest": "^4.1.11",
128
133
  "zod": "^4.4.3"