billion-context-dsh 0.2.20 → 0.2.21

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/README.en.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [English](./README.en.md) | [中文](./README.md)
4
4
 
5
5
  > **⚠️ Beta notice — not for production use**
6
- > This project (**v0.2.20**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
6
+ > This project (**v0.2.21**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
7
7
 
8
8
  <p align="center">
9
9
  <strong>Built with gratitude on top of these projects</strong> — please give them a ⭐:
@@ -98,7 +98,7 @@ This only installs the package into your project/global store; it does **not** t
98
98
  **Install from the git source (`github:` spec — the form the plugin store shows).** The prebuilt `dist/` artifacts are committed to this repository, so a git-source install also works out of the box — **no build step needed**, and pnpm 11's default build-script blocking (`allowBuilds`) never applies to this package:
99
99
 
100
100
  ```bash
101
- dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.20
101
+ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.21
102
102
  ```
103
103
 
104
104
  Prefer a `#<tag>` ref to get artifacts identical to that npm release; without a ref you get the latest default-branch build. Only building the repo yourself (`npm run build`) requires approving build scripts. Background and trade-offs: [docs/git-source-install-design.md](docs/git-source-install-design.md) (issue #92).
@@ -179,7 +179,7 @@ DSH derives every model request from its append-only session log (the *surface*)
179
179
  | `acp_status` | CONTEXT BREAKDOWN (tool/text/summaries shares of the visible total) + compressed-block ledger + nudge decision line + a `Checkpoint seqs` row mapping each ACTIVE block's kernel ref (`bN`) to its checkpoint summary seq — compressing a checkpoint seq distills that block (issue #60); no context-window rows; scope/view/tool/sort/limit drilldown supported |
180
180
  | block state | in-memory kernel state + **log-rebuilt ledger** (no sidecar files) |
181
181
  | tiered distillation (T2/T3) | re-compressing a block's summary node distills that block (tier 2); distilling a tier-2 block yields tier 3. Tier + kernel block ids are persisted to the log, so kernel state rehydrates from the log after a restart and stays distillable |
182
- | compression accounting (shadow price) | `shadowedTokenCount` (what the host occupancy display deducts) is priced in the **host token-meter's vocabulary** (`ctx.tokenMeter.measure` preferred; exact mirror in `src/host-tokens.ts` as fallback) — never the plugin's internal CJK-aware estimate (that is display currency; mixing it into the host ledger can drive `messageTokens` negative and brick a CJK-heavy session, issue #54) |
182
+ | compression accounting (shadow price) | `shadowedTokenCount` (what the host occupancy display deducts) is priced with the **host token-meter's fixed-heuristic price** (`ctx.tokenMeter.measure` preferred, reading the `heuristicTokens ?? tokens` fixed-heuristic basis; exact mirror in `src/host-tokens.ts` as fallback) — never the plugin's internal CJK-aware estimate (that is display currency; mixing it into the host ledger can drive `messageTokens` negative and brick a CJK-heavy session, issue #54), and never the route-repriced `node.tokens` either (under 0.1.2+ image route pricing that is request-pressure currency; summing it overstates an image range's claim and folds the same ledger negative, issue #103) |
183
183
 
184
184
  The load-bearing compression guidance (tools, philosophy, summary rules, tier rules) is registered as a one-time system-prompt section; each nudge carries a condensed version (efficiency note + philosophy + context breakdown + HOW_TO_COMPRESS_RULES + range table + batch tip). There is deliberately **no automatic summarization**: automatic policy only nudges the model (`compactIfNeeded` returns null).
185
185
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [中文](./README.md) | [English](./README.en.md)
4
4
 
5
5
  > **⚠️ 测试版声明——请勿用于生产环境**
6
- > 本项目(**v0.2.20**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
6
+ > 本项目(**v0.2.21**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
7
7
 
8
8
  <p align="center">
9
9
  <strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
@@ -97,7 +97,7 @@ npm install billion-context-dsh
97
97
  **git 源安装(`github:` 规格,插件商店展示的形态)。** 预构建产物 `dist/` 已提交到仓库,从 git 源安装同样开箱即用——**无需任何构建步骤**,pnpm 11 默认拦截构建脚本(`allowBuilds`)的机制对这个包不构成障碍:
98
98
 
99
99
  ```bash
100
- dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.20
100
+ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.21
101
101
  ```
102
102
 
103
103
  建议带 `#<tag>` 安装,拿到与对应 npm 版本完全一致的产物;不带 ref 则装默认分支的最新构建。只有 clone 仓库自行从源码构建(`npm run build`)才需要放行构建。背景与方案取舍见 [docs/git-source-install-design.md](docs/git-source-install-design.md)(issue #92)。
@@ -176,7 +176,7 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
176
176
  | `acp_status` | CONTEXT BREAKDOWN(tool/text/summaries 占可见总量)+ 压缩块账本 + nudge 决策行 + `Checkpoint seqs` 行(active 块的 `bN → seq` 映射——压缩某个 checkpoint seq 即蒸馏该块,issue #60);不含上下文窗口;支持 scope/view/tool/sort/limit 钻取 |
177
177
  | 块状态 | 内存内核状态 + **日志重建账本**(无旁车文件) |
178
178
  | 分层蒸馏(T2/T3) | 再次压缩某块的摘要节点 = 蒸馏该块(tier 2),蒸馏 tier-2 块得 tier 3;tier 与内核块 id 持久化进日志,重启后内核状态从日志再水合、可继续蒸馏 |
179
- | 压缩记账(影子价格) | `shadowedTokenCount`(宿主占用率据此扣减)**用宿主 token-meter 的词汇计价**(`ctx.tokenMeter.measure` 优先,`src/host-tokens.ts` 精确镜像兜底)——绝不混用插件内部的 CJK 感知估算(那是展示货币,混用会把宿主账本扣成负数、卡死中文会话,issue #54) |
179
+ | 压缩记账(影子价格) | `shadowedTokenCount`(宿主占用率据此扣减)**用宿主 token-meter 的固定启发价计价**(`ctx.tokenMeter.measure` 优先、按 `heuristicTokens ?? tokens` 读固定启发价基准,`src/host-tokens.ts` 精确镜像兜底)——绝不混用插件内部的 CJK 感知估算(那是展示货币,混用会把宿主账本扣成负数、卡死中文会话,issue #54),也不按路由重定价的 `node.tokens` 计价(0.1.2+ 图片路由计价下那是请求压力价,读它会让含图片区间的 claim 虚报视觉价、同样扣穿账本,issue #103) |
180
180
 
181
181
  承载性的压缩指引(工具、哲学、摘要规则、tier 蒸馏/浓缩规则)注册为一次性系统提示段;每条 nudge 携带精简版(效率提示 + 哲学 + 上下文分解 + 压缩规则 + 范围表 + 批量提示)。刻意**不做自动摘要**:自动策略只 nudge 模型(`compactIfNeeded` 返回 null)。
182
182
 
@@ -11,10 +11,20 @@
11
11
  * display currency, NEVER event currency).
12
12
  *
13
13
  * This module prices claims in the host's vocabulary: it prefers the live
14
- * meter's own per-node prices (`ctx.tokenMeter.measure(session).nodes`
15
- * exact by construction, follows host estimator changes automatically, the
16
- * same path the host's own `compaction-basic` uses) and falls back to an
14
+ * meter's own per-node FIXED-HEURISTIC prices (`ctx.tokenMeter.measure(session)`
15
+ * nodes' `heuristicTokens` the same basis the projection ledger accumulates
16
+ * appends with, so the claim is exact by construction) and falls back to an
17
17
  * exact mirror of the host's estimator when the meter is unreachable.
18
+ *
19
+ * Two vocabularies share the meter's node since DSH 0.1.2: `tokens` carries
20
+ * the measured route's request pressure (image occurrences re-priced with the
21
+ * route's declared visual tokens) while `heuristicTokens` keeps the fixed
22
+ * flat-4 heuristic the ledger prices appends with. The claim MUST read
23
+ * `heuristicTokens`: a routed `tokens` claim overstates the replaced range
24
+ * against its own ledger accumulation and folds `messageTokens` negative —
25
+ * the same session-bricking schema rejection as #54, through the image-route
26
+ * channel (issue #103). Older hosts (0.1.0/0.1.1 lines) expose a single
27
+ * `tokens` field that IS the fixed heuristic, so the fallback reads it.
18
28
  */
19
29
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
20
30
  /** The host's model-visible content block union (structural, mirror-side only). */
@@ -61,8 +71,17 @@ export declare function hostPriceEvent(event: SessionEvent): number;
61
71
  export declare function shadowedHostTokens(session: Session, seqs: readonly number[]): number;
62
72
  /**
63
73
  * Claim price for `seqs` in the host's vocabulary. Prefers the live meter's
64
- * own per-node prices when `ctx.tokenMeter` is reachable and covers every
65
- * shadowed seq (exact by construction, follows host estimator changes); ANY
74
+ * own per-node FIXED-HEURISTIC prices when `ctx.tokenMeter` is reachable and
75
+ * covers every shadowed seq (exact by construction the ledger's
76
+ * `foldSurfaceProjection` accumulates appends with the same fixed heuristic,
77
+ * so the claim and the ledger stay in agreement; follows host estimator
78
+ * changes automatically). `node.heuristicTokens` is that basis since DSH 0.1.2;
79
+ * `node.tokens` there is the measured route's REQUEST pressure (image
80
+ * occurrences carry the route's visual price via `priceSurface`) and MUST NOT
81
+ * be claimed — reading it overstates the claim and folds the host projection
82
+ * negative on image-containing ranges (issue #103, the image-route channel of
83
+ * the #54 brick). Older meters expose a single `tokens` field that IS the
84
+ * fixed heuristic, so `heuristicTokens ?? tokens` covers both shapes. ANY
66
85
  * failure — meter absent, `measure` throwing (e.g. a step-less log), or a seq
67
86
  * missing from the measurement — falls back to the exact mirror. Never returns
68
87
  * a `defaultCountTokens` price (rule 12).
package/dist/index.js CHANGED
@@ -2637,13 +2637,7 @@ function makePreview(text, query, len) {
2637
2637
 
2638
2638
  // src/region.ts
2639
2639
  import { randomUUID } from "crypto";
2640
- import {
2641
- CompactionId,
2642
- compactCheckpointSource,
2643
- toolPairingBalancedAfter,
2644
- toolPairingBalancedBefore
2645
- } from "@deepseek-ai/dsh-compaction";
2646
- import { createAssistantMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
2640
+ import { CompactionId, compactCheckpointSource } from "@deepseek-ai/dsh-compaction";
2647
2641
 
2648
2642
  // src/session-events.ts
2649
2643
  function sessionEventsOf(session) {
@@ -2657,6 +2651,78 @@ function eventAtOf(session, seq) {
2657
2651
  return session.events[seq];
2658
2652
  }
2659
2653
 
2654
+ // src/tool-pairing.ts
2655
+ var balanceCacheBySession = /* @__PURE__ */ new WeakMap();
2656
+ function eventDelta(event) {
2657
+ if (event.type === "tool/result") return -1;
2658
+ if (event.type === "assistant/message") {
2659
+ const content = event.data.message?.content;
2660
+ if (!Array.isArray(content)) return 0;
2661
+ let calls = 0;
2662
+ for (const block of content) {
2663
+ if (block !== null && typeof block === "object" && block.type === "tool-call") calls += 1;
2664
+ }
2665
+ return calls;
2666
+ }
2667
+ return 0;
2668
+ }
2669
+ function eventForSeq(session, seq) {
2670
+ const event = eventAtOf(session, seq);
2671
+ if (event === void 0 || event.seq !== seq) {
2672
+ throw new Error(`tool-pairing balance: surface seq ${seq} has no matching session event (corrupt surface)`);
2673
+ }
2674
+ return event;
2675
+ }
2676
+ function extendCache(session, cache2, seqs) {
2677
+ const processed = cache2.cutBalanced.length - 1;
2678
+ const tail = seqs.slice(processed);
2679
+ const pendingCuts = [];
2680
+ let inProgressToolCalls = cache2.inProgressToolCalls;
2681
+ for (const seq of tail) {
2682
+ inProgressToolCalls += eventDelta(eventForSeq(session, seq));
2683
+ if (inProgressToolCalls < 0) {
2684
+ throw new Error(`tool-pairing balance: tool/result at surface seq ${seq} has no matching tool-call (corrupt surface)`);
2685
+ }
2686
+ pendingCuts.push(inProgressToolCalls === 0);
2687
+ }
2688
+ tail.forEach((seq, offset) => cache2.indexBySeq.set(seq, processed + offset));
2689
+ cache2.cutBalanced = cache2.cutBalanced.concat(pendingCuts);
2690
+ cache2.inProgressToolCalls = inProgressToolCalls;
2691
+ return cache2;
2692
+ }
2693
+ function balanceCache(session) {
2694
+ const seqs = session.surface.nodes;
2695
+ const generation = session.surface.replaceGeneration;
2696
+ const cached = balanceCacheBySession.get(session);
2697
+ if (cached === void 0 || cached.generation !== generation || cached.cutBalanced.length - 1 > seqs.length) {
2698
+ const rebuilt = extendCache(session, {
2699
+ generation,
2700
+ cutBalanced: [true],
2701
+ indexBySeq: /* @__PURE__ */ new Map(),
2702
+ inProgressToolCalls: 0
2703
+ }, seqs);
2704
+ balanceCacheBySession.set(session, rebuilt);
2705
+ return rebuilt;
2706
+ }
2707
+ if (cached.cutBalanced.length - 1 < seqs.length) return extendCache(session, cached, seqs);
2708
+ return cached;
2709
+ }
2710
+ function cutBalance(cache2, seq, offset) {
2711
+ const index = cache2.indexBySeq.get(seq);
2712
+ const balanced = index === void 0 ? void 0 : cache2.cutBalanced[index + offset];
2713
+ if (balanced === void 0) throw new Error(`tool-pairing balance: surface seq ${seq} not found`);
2714
+ return balanced;
2715
+ }
2716
+ function toolPairingBalancedBefore(session, seq) {
2717
+ return cutBalance(balanceCache(session), seq, 0);
2718
+ }
2719
+ function toolPairingBalancedAfter(session, seq) {
2720
+ return cutBalance(balanceCache(session), seq, 1);
2721
+ }
2722
+
2723
+ // src/region.ts
2724
+ import { createAssistantMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
2725
+
2660
2726
  // src/messages.ts
2661
2727
  function extractText(content) {
2662
2728
  if (typeof content === "string") return content;
@@ -2847,7 +2913,7 @@ function shadowedTokensViaMeter(session, seqs, ctx) {
2847
2913
  try {
2848
2914
  const meter = ctx?.get?.("tokenMeter");
2849
2915
  if (meter?.measure !== void 0) {
2850
- const bySeq = new Map(meter.measure(session).nodes.map((node) => [node.seq, node.tokens]));
2916
+ const bySeq = new Map(meter.measure(session).nodes.map((node) => [node.seq, node.heuristicTokens ?? node.tokens]));
2851
2917
  let total = 0;
2852
2918
  let missing = false;
2853
2919
  for (const seq of seqs) {