billion-context-dsh 0.2.19 → 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 +6 -6
- package/README.md +6 -6
- package/dist/host-tokens.d.ts +24 -5
- package/dist/index.d.ts +25 -1
- package/dist/index.js +132 -19
- package/dist/index.js.map +1 -1
- package/dist/tool-pairing.d.ts +36 -0
- package/dist/window.d.ts +43 -5
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
|
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
|
|
|
@@ -216,8 +216,8 @@ This project reuses `acp-kernel`'s compression core and `billion-context-pi`'s d
|
|
|
216
216
|
|
|
217
217
|
| Key | Default | Meaning |
|
|
218
218
|
|---|---|---|
|
|
219
|
-
| `modelContextLimit` | auto-detected (fallback `128000`) | Context window used for the kernel's pressure decisions; an explicit value wins and skips detection. When omitted, the host session projection `contextPressure.contextWindow` is read first — the capacity disclosed for the **current real route** (a session that switched models follows automatically, no restart needed) — and the model API is probed only when the projection discloses no window |
|
|
220
|
-
| `autoModelContextLimit` | `true` | Resolve the real context window automatically: the host projection first (`windowFor` → `projectedContextWindow`, `src/window.ts`), then the model API probe (`agent.ctx.llm.resolveModelInfo`); both are skipped when `autoModelContextLimit: false`. On probe failure it falls back to the default, and the `/acp` command shows the window source (the `acp_status` model tool carries no window info). A failed probe is surfaced in the host log and the `/acp` panel (`restart to re-probe`) — the failure is cached like a success, so fixing the gateway requires a restart or an explicit `modelContextLimit` before the probe retries |
|
|
219
|
+
| `modelContextLimit` | auto-detected (fallback `128000`) | Context window used for the kernel's pressure decisions; an explicit value wins and skips detection. When omitted, the host session projection `contextPressure.contextWindow` is read first — the capacity disclosed for the **current real route** (a session that switched models follows automatically, no restart needed) — and the model API is probed only when the projection discloses no window; an explicit value also skips the output-reservation subtraction (the operator owns the denominator) |
|
|
220
|
+
| `autoModelContextLimit` | `true` | Resolve the real context window automatically: the host projection first (`windowFor` → `projectedContextWindow`, `src/window.ts`), then the model API probe (`agent.ctx.llm.resolveModelInfo`); both are skipped when `autoModelContextLimit: false`. On probe failure it falls back to the default, and the `/acp` command shows the window source (the `acp_status` model tool carries no window info). A failed probe is surfaced in the host log and the `/acp` panel (`restart to re-probe`) — the failure is cached like a success, so fixing the gateway requires a restart or an explicit `modelContextLimit` before the probe retries. On a successful probe the adapter's per-request output cap (`defaultMaxTokens` — the output reservation the provider guarantees at the end of the window) is SUBTRACTED, so every downstream pressure decision (nudge tiers, truncate, growth) measures usage against the SUSTAINABLE input budget (window − reservation): a 96K window with a 16K cap carries at most 80K of input, and the raw denominator understated usage by cap/window (≈17% there — and the ratio is far higher on short-window models, where the same cap is a quarter or more of the window). When the cap is undisclosed, the limit is explicit, or the probe fails, the raw-window behavior is kept; `/acp status` shows the subtraction (raw − reservation) |
|
|
221
221
|
| `nudgeMinContextLimitPct` | kernel default `0.45` | Nudge window lower bound (usage fraction) — validation only; the growth-driven trigger has no percentage floor — same default as billion-context-pi |
|
|
222
222
|
| `nudgeMaxContextLimitPct` | engine default `0.70` (kernel/pi default `0.75`) | Over-limit line: above this the nudge fires regardless of growth — deliberately below the host compaction-basic 80% auto-compaction line so the forced nudge fires first; an explicit value wins (a same-name key in `coreOverrides.nudge` outranks it — see below) |
|
|
223
223
|
| `nudgeEmergencyThresholdPct` | engine default `0.85` (kernel/pi default `0.95`) | Emergency nudge (bypasses the per-turn dedup) — lowered from `0.95`: at 95% the model has no room to act and the 80% auto-compaction line shadows it; an explicit value wins (a same-name key in `coreOverrides.nudge` outranks it — see below) |
|
|
@@ -250,7 +250,7 @@ src/
|
|
|
250
250
|
├── nudge.ts # M4: kernel pressure decision → injected advisory nudge
|
|
251
251
|
├── system-prompt.ts# M4: one-time ACP guidance section (keeps nudges short)
|
|
252
252
|
├── config.ts # kernel config assembly (thresholds + coreOverrides)
|
|
253
|
-
├── window.ts # auto context-window detection (session projection first, LLM runtime probe fallback, default 128000)
|
|
253
|
+
├── window.ts # auto context-window detection (session projection first, LLM runtime probe fallback, default 128000) + output-reservation probe (defaultMaxTokens, subtracted in windowFor)
|
|
254
254
|
└── commands.ts # M4: /acp slash command
|
|
255
255
|
```
|
|
256
256
|
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[中文](./README.md) | [English](./README.en.md)
|
|
4
4
|
|
|
5
5
|
> **⚠️ 测试版声明——请勿用于生产环境**
|
|
6
|
-
> 本项目(**v0.2.
|
|
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.
|
|
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
|
|
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
|
|
|
@@ -213,8 +213,8 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
|
|
|
213
213
|
|
|
214
214
|
| 键 | 默认值 | 含义 |
|
|
215
215
|
|---|---|---|
|
|
216
|
-
| `modelContextLimit` | 自动探测(回退 `128000`) | 用于内核压力决策的上下文窗口;显式配置时优先且跳过探测。省略时优先读宿主会话投影 `contextPressure.contextWindow`(按**当前真实路由**披露的新窗口,切模型会话自动跟随,无需重启),无投影时再从模型 API
|
|
217
|
-
| `autoModelContextLimit` | `true` | 从模型 API 自动探测真实窗口(`agent.ctx.llm.resolveModelInfo`);探测失败回退默认值,`/acp` 命令展示窗口来源(模型工具 `acp_status` 不含窗口信息)。省略时窗口先读宿主投影(`windowFor` → `projectedContextWindow`,`src/window.ts`)再走探测;投影与探测在 `autoModelContextLimit: false` 时均跳过。探测失败会在宿主日志与 `/acp` 面板提示(`restart to re-probe`)——失败结果同样被缓存,修复网关后需重启或显式设置 `modelContextLimit`
|
|
216
|
+
| `modelContextLimit` | 自动探测(回退 `128000`) | 用于内核压力决策的上下文窗口;显式配置时优先且跳过探测。省略时优先读宿主会话投影 `contextPressure.contextWindow`(按**当前真实路由**披露的新窗口,切模型会话自动跟随,无需重启),无投影时再从模型 API 探测;显式配置同样跳过输出预留扣减(分母完全由操作者定义) |
|
|
217
|
+
| `autoModelContextLimit` | `true` | 从模型 API 自动探测真实窗口(`agent.ctx.llm.resolveModelInfo`);探测失败回退默认值,`/acp` 命令展示窗口来源(模型工具 `acp_status` 不含窗口信息)。省略时窗口先读宿主投影(`windowFor` → `projectedContextWindow`,`src/window.ts`)再走探测;投影与探测在 `autoModelContextLimit: false` 时均跳过。探测失败会在宿主日志与 `/acp` 面板提示(`restart to re-probe`)——失败结果同样被缓存,修复网关后需重启或显式设置 `modelContextLimit` 才会重新探测。探测成功后还会**扣减 adapter 的每请求输出上限**(`defaultMaxTokens`,窗口末端每请求保证的输出预留):所有下游压力决策(nudge 档位、truncate、growth)以「可持续输入预算」(窗口 − 输出预留)为分母——96K 窗口 + 16K 上限实际最多承载 80K 输入,原裸窗口分母会把用量低估 cap/window(此处 ≈17%;小上下文窗口模型比例更高);上限未披露、显式配置或探测失败时保持裸窗口行为,`/acp status` 展示扣减明细(raw − reservation) |
|
|
218
218
|
| `nudgeMinContextLimitPct` | 内核默认 `0.45` | Nudge 窗口下界(用量占比)——仅作配置校验,增长路径的触发没有百分比下限——与 billion-context-pi 相同的默认值 |
|
|
219
219
|
| `nudgeMaxContextLimitPct` | engine 默认 `0.70`(内核/pi 默认 `0.75`) | 过限线:超过此值则无论增长与否都触发 nudge——刻意低于宿主 compaction-basic 的 80% 自动压缩线,保证强制 nudge 先触发;显式配置优先(`coreOverrides.nudge` 同名键优先级更高,见下) |
|
|
220
220
|
| `nudgeEmergencyThresholdPct` | engine 默认 `0.85`(内核/pi 默认 `0.95`) | 紧急 nudge(绕过每轮去重)——从 `0.95` 下调:95% 时模型已无操作空间且会被 80% 自动压缩线遮蔽;显式配置优先(`coreOverrides.nudge` 同名键优先级更高,见下) |
|
|
@@ -247,7 +247,7 @@ src/
|
|
|
247
247
|
├── nudge.ts # M4: 内核压力决策 → 注入的建议式 nudge
|
|
248
248
|
├── system-prompt.ts# M4: 一次性 ACP 指引段(让 nudge 保持简短)
|
|
249
249
|
├── config.ts # 内核配置组装(阈值 + coreOverrides)
|
|
250
|
-
├── window.ts # 自动上下文窗口探测(宿主投影优先,LLM 运行时探测回退,兜底 128000
|
|
250
|
+
├── window.ts # 自动上下文窗口探测(宿主投影优先,LLM 运行时探测回退,兜底 128000)+ 输出预留探测(defaultMaxTokens,windowFor 内扣除)
|
|
251
251
|
└── commands.ts # M4: /acp 斜杠命令
|
|
252
252
|
```
|
|
253
253
|
|
package/dist/host-tokens.d.ts
CHANGED
|
@@ -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)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
65
|
-
* shadowed seq (exact by construction
|
|
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.d.ts
CHANGED
|
@@ -128,6 +128,8 @@ export declare class AcpCompactionEngine extends CompactionEngine {
|
|
|
128
128
|
private readonly compressCallIdsToHide;
|
|
129
129
|
/** Per provider/model route the resolved window (probe failures cached too). */
|
|
130
130
|
private readonly windowCache;
|
|
131
|
+
/** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */
|
|
132
|
+
private readonly outputReservationCache;
|
|
131
133
|
constructor(ctx: Context, config?: Partial<AcpConfig>);
|
|
132
134
|
/**
|
|
133
135
|
* Resolve the effective context window for an agent. An explicitly
|
|
@@ -138,9 +140,31 @@ export declare class AcpCompactionEngine extends CompactionEngine {
|
|
|
138
140
|
* projectedContextWindow). Falls back to probing the model's real window
|
|
139
141
|
* via `agent.ctx.llm.resolveModelInfo` (cached per provider/model route,
|
|
140
142
|
* probe failures cached too) and finally to DEFAULT_CONTEXT_WINDOW when
|
|
141
|
-
* auto-detection is disabled or unavailable.
|
|
143
|
+
* auto-detection is disabled or unavailable. On the auto-detected paths the
|
|
144
|
+
* adapter's per-request output cap is then SUBTRACTED from the window
|
|
145
|
+
* (applyReservation): every downstream usage computation must run against
|
|
146
|
+
* the SUSTAINABLE input budget (window minus output reservation), not the
|
|
147
|
+
* raw window — a 96K window with a 16K cap carries at most 80K of input,
|
|
148
|
+
* so the raw denominator understates usage by cap/window (≈17% there, and
|
|
149
|
+
* far worse on short-window models). An explicit limit keeps the operator's
|
|
150
|
+
* exact value (they own the denominator); a failed probe keeps the raw
|
|
151
|
+
* fallback.
|
|
142
152
|
*/
|
|
143
153
|
windowFor(agent: Agent): Promise<AcpWindow>;
|
|
154
|
+
/**
|
|
155
|
+
* The adapter's per-request output cap for a route, from one
|
|
156
|
+
* probeModelWindow call (a local catalog lookup — no request is sent),
|
|
157
|
+
* cached per route like the window itself.
|
|
158
|
+
*/
|
|
159
|
+
private outputCapFor;
|
|
160
|
+
/**
|
|
161
|
+
* Subtract the output reservation from a resolved window: `limit` becomes
|
|
162
|
+
* the SUSTAINABLE input budget (`rawLimit - outputReserved`) that every
|
|
163
|
+
* downstream usage computation (nudge tiers, truncate, growth) measures
|
|
164
|
+
* against. No-op when the cap is unknown or not smaller than the window
|
|
165
|
+
* (degenerate config) — the raw-window behavior is preserved.
|
|
166
|
+
*/
|
|
167
|
+
private applyReservation;
|
|
144
168
|
/** ACP is model-driven: automatic pressure policy never summarizes by itself. */
|
|
145
169
|
compactIfNeeded(_agent: CompactionAgentContext, _trigger: CompactionTrigger, signal: AbortSignal): Promise<CompactionResult | null>;
|
|
146
170
|
/** Explicit idle-session compaction: ACP leaves the decision to the model. */
|
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) {
|
|
@@ -4383,18 +4449,24 @@ function projectedContextWindow(agent) {
|
|
|
4383
4449
|
if (typeof window === "number" && Number.isInteger(window) && window > 0) return window;
|
|
4384
4450
|
return null;
|
|
4385
4451
|
}
|
|
4386
|
-
async function
|
|
4452
|
+
async function probeModelWindow(agent, provider, model) {
|
|
4387
4453
|
const llm = agent.ctx?.get?.("llm");
|
|
4388
|
-
if (llm?.resolveModelInfo === void 0) return null;
|
|
4454
|
+
if (llm?.resolveModelInfo === void 0) return { contextWindow: null, outputReservation: null };
|
|
4389
4455
|
try {
|
|
4390
4456
|
const info = await llm.resolveModelInfo(provider, model);
|
|
4391
4457
|
const window = info?.context?.contextWindow;
|
|
4392
|
-
|
|
4393
|
-
return
|
|
4458
|
+
const cap = info?.defaultMaxTokens;
|
|
4459
|
+
return {
|
|
4460
|
+
contextWindow: typeof window === "number" && Number.isInteger(window) && window > 0 ? window : null,
|
|
4461
|
+
outputReservation: typeof cap === "number" && Number.isInteger(cap) && cap > 0 ? cap : null
|
|
4462
|
+
};
|
|
4394
4463
|
} catch {
|
|
4395
|
-
return null;
|
|
4464
|
+
return { contextWindow: null, outputReservation: null };
|
|
4396
4465
|
}
|
|
4397
4466
|
}
|
|
4467
|
+
async function detectContextWindow(agent, provider, model) {
|
|
4468
|
+
return (await probeModelWindow(agent, provider, model)).contextWindow;
|
|
4469
|
+
}
|
|
4398
4470
|
|
|
4399
4471
|
// src/commands.ts
|
|
4400
4472
|
async function statusText(env, agent) {
|
|
@@ -4406,12 +4478,13 @@ async function statusText(env, agent) {
|
|
|
4406
4478
|
const estimated = resolveTokenCount(agent, surfaceMessages);
|
|
4407
4479
|
const window = await resolveEffectiveWindow(env, agent);
|
|
4408
4480
|
const limit = window.limit;
|
|
4481
|
+
const windowLine = window.rawLimit !== void 0 && window.outputReserved !== void 0 ? ` context window: ${limit} (raw ${window.rawLimit} \u2212 ${window.outputReserved} output reservation; ${windowSourceLabel(window)})` : ` context window: ${limit} (${windowSourceLabel(window)})`;
|
|
4409
4482
|
const lines = [
|
|
4410
4483
|
`ACP status \u2014 session ${session.id}`,
|
|
4411
4484
|
` blocks: ${ledger.length}`,
|
|
4412
4485
|
` tokens compressed: ${totalTokens}`,
|
|
4413
4486
|
` estimated context: ${estimated} / ${limit} (${Math.round(estimated / limit * 100)}%)`,
|
|
4414
|
-
|
|
4487
|
+
windowLine
|
|
4415
4488
|
];
|
|
4416
4489
|
if (window.probeFailed === true) {
|
|
4417
4490
|
lines.push(` \u26A0 window auto-detection failed \u2014 using the ${limit} fallback (restart to re-probe, or set modelContextLimit explicitly)`);
|
|
@@ -4540,6 +4613,8 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
4540
4613
|
compressCallIdsToHide = /* @__PURE__ */ new Set();
|
|
4541
4614
|
/** Per provider/model route the resolved window (probe failures cached too). */
|
|
4542
4615
|
windowCache = /* @__PURE__ */ new Map();
|
|
4616
|
+
/** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */
|
|
4617
|
+
outputReservationCache = /* @__PURE__ */ new Map();
|
|
4543
4618
|
constructor(ctx, config = {}) {
|
|
4544
4619
|
super(ctx);
|
|
4545
4620
|
this.config = resolveAcpConfig(config);
|
|
@@ -4648,7 +4723,15 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
4648
4723
|
* projectedContextWindow). Falls back to probing the model's real window
|
|
4649
4724
|
* via `agent.ctx.llm.resolveModelInfo` (cached per provider/model route,
|
|
4650
4725
|
* probe failures cached too) and finally to DEFAULT_CONTEXT_WINDOW when
|
|
4651
|
-
* auto-detection is disabled or unavailable.
|
|
4726
|
+
* auto-detection is disabled or unavailable. On the auto-detected paths the
|
|
4727
|
+
* adapter's per-request output cap is then SUBTRACTED from the window
|
|
4728
|
+
* (applyReservation): every downstream usage computation must run against
|
|
4729
|
+
* the SUSTAINABLE input budget (window minus output reservation), not the
|
|
4730
|
+
* raw window — a 96K window with a 16K cap carries at most 80K of input,
|
|
4731
|
+
* so the raw denominator understates usage by cap/window (≈17% there, and
|
|
4732
|
+
* far worse on short-window models). An explicit limit keeps the operator's
|
|
4733
|
+
* exact value (they own the denominator); a failed probe keeps the raw
|
|
4734
|
+
* fallback.
|
|
4652
4735
|
*/
|
|
4653
4736
|
async windowFor(agent) {
|
|
4654
4737
|
if (this.config.modelContextLimit !== void 0) {
|
|
@@ -4660,28 +4743,58 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
4660
4743
|
if (this.config.autoModelContextLimit) {
|
|
4661
4744
|
const projected = projectedContextWindow(agent);
|
|
4662
4745
|
if (projected !== null) {
|
|
4663
|
-
|
|
4746
|
+
const cap2 = await this.outputCapFor(agent, provider, model);
|
|
4747
|
+
return this.applyReservation({ limit: projected, source: "projection", provider, model }, cap2);
|
|
4664
4748
|
}
|
|
4665
4749
|
}
|
|
4666
4750
|
const cached = this.windowCache.get(key);
|
|
4667
4751
|
if (cached !== void 0) return cached;
|
|
4668
4752
|
let window;
|
|
4753
|
+
let cap = null;
|
|
4669
4754
|
if (!this.config.autoModelContextLimit) {
|
|
4670
4755
|
window = { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model };
|
|
4671
4756
|
} else {
|
|
4672
|
-
const
|
|
4673
|
-
|
|
4757
|
+
const probe = await probeModelWindow(agent, provider, model);
|
|
4758
|
+
cap = probe.outputReservation;
|
|
4759
|
+
if (probe.contextWindow === null) {
|
|
4674
4760
|
this.ctx.logger.warn(
|
|
4675
4761
|
`billion-context-dsh: context-window auto-detection failed for ${provider}/${model} \u2014 using the ${DEFAULT_CONTEXT_WINDOW} fallback (restart to re-probe, or set modelContextLimit explicitly)`
|
|
4676
4762
|
);
|
|
4677
4763
|
window = { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model, probeFailed: true };
|
|
4764
|
+
cap = null;
|
|
4678
4765
|
} else {
|
|
4679
|
-
window = { limit:
|
|
4766
|
+
window = { limit: probe.contextWindow, source: "auto", provider, model };
|
|
4680
4767
|
}
|
|
4681
4768
|
}
|
|
4769
|
+
window = this.applyReservation(window, cap);
|
|
4682
4770
|
this.windowCache.set(key, window);
|
|
4683
4771
|
return window;
|
|
4684
4772
|
}
|
|
4773
|
+
/**
|
|
4774
|
+
* The adapter's per-request output cap for a route, from one
|
|
4775
|
+
* probeModelWindow call (a local catalog lookup — no request is sent),
|
|
4776
|
+
* cached per route like the window itself.
|
|
4777
|
+
*/
|
|
4778
|
+
async outputCapFor(agent, provider, model) {
|
|
4779
|
+
if (provider === "" || model === "") return null;
|
|
4780
|
+
const key = `${provider}\0${model}`;
|
|
4781
|
+
const known = this.outputReservationCache.get(key);
|
|
4782
|
+
if (known !== void 0) return known;
|
|
4783
|
+
const cap = (await probeModelWindow(agent, provider, model)).outputReservation;
|
|
4784
|
+
this.outputReservationCache.set(key, cap);
|
|
4785
|
+
return cap;
|
|
4786
|
+
}
|
|
4787
|
+
/**
|
|
4788
|
+
* Subtract the output reservation from a resolved window: `limit` becomes
|
|
4789
|
+
* the SUSTAINABLE input budget (`rawLimit - outputReserved`) that every
|
|
4790
|
+
* downstream usage computation (nudge tiers, truncate, growth) measures
|
|
4791
|
+
* against. No-op when the cap is unknown or not smaller than the window
|
|
4792
|
+
* (degenerate config) — the raw-window behavior is preserved.
|
|
4793
|
+
*/
|
|
4794
|
+
applyReservation(window, cap) {
|
|
4795
|
+
if (cap === null || cap >= window.limit) return window;
|
|
4796
|
+
return { ...window, rawLimit: window.limit, outputReserved: cap, limit: window.limit - cap };
|
|
4797
|
+
}
|
|
4685
4798
|
/** ACP is model-driven: automatic pressure policy never summarizes by itself. */
|
|
4686
4799
|
async compactIfNeeded(_agent, _trigger, signal) {
|
|
4687
4800
|
signal.throwIfAborted();
|