billion-context-dsh 0.1.0 → 0.1.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/README.md +91 -50
- package/README.zh-CN.md +90 -52
- package/dist/index.js +114 -34
- package/dist/index.js.map +1 -1
- package/dist/nudge.d.ts +6 -0
- package/dist/region.d.ts +26 -6
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,23 +1,69 @@
|
|
|
1
1
|
# billion-context-dsh
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](./README.md) | [中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **⚠️ Beta notice — not for production use**
|
|
6
|
+
> This project (**v0.1.1**) 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
7
|
|
|
7
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<strong>Built with gratitude on top of these projects</strong> — please give them a ⭐:
|
|
10
|
+
<br />
|
|
11
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> ·
|
|
12
|
+
<a href="https://github.com/ranxianglei/billion-context-pi">billion-context-pi</a> ·
|
|
13
|
+
<a href="https://github.com/ranxianglei/acp-kernel">acp-kernel</a> ·
|
|
14
|
+
<a href="https://github.com/ranxianglei/opencode-acp">opencode-acp</a>
|
|
15
|
+
</p>
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
<p align="center">
|
|
18
|
+
<strong>Billion-Context</strong> for <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a>
|
|
19
|
+
<br />
|
|
20
|
+
The model decides <em>when</em> and <em>what</em> to compress — not a hard limit.
|
|
21
|
+
</p>
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
---
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
<p align="center">
|
|
26
|
+
<a href="https://www.npmjs.com/package/billion-context-dsh"><img src="https://img.shields.io/npm/v/billion-context-dsh.svg?style=flat-square" alt="npm"></a>
|
|
27
|
+
<a href="https://github.com/Tyan66666/billion-context-dsh/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/billion-context-dsh.svg?style=flat-square" alt="license"></a>
|
|
28
|
+
<a href="https://github.com/Tyan66666/billion-context-dsh"><img src="https://img.shields.io/badge/GitHub-Tyan66666%2Fbillion--context--dsh-181717?style=flat-square&logo=github" alt="GitHub"></a>
|
|
29
|
+
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/topic-dsh--plugin-blue?style=flat-square" alt="dsh-plugin"></a>
|
|
30
|
+
</p>
|
|
19
31
|
|
|
20
|
-
|
|
32
|
+
<p align="center">
|
|
33
|
+
<code>npm install billion-context-dsh</code>
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why?
|
|
39
|
+
|
|
40
|
+
When conversations get long, the model runs out of context. Most tools hard-truncate — silently dropping earlier messages. **billion-context-dsh** gives the model a `compress` tool: the LLM decides **when** and **what** to compress into high-fidelity summaries, preserving critical details (file paths, decisions, error strings) while reclaiming context space.
|
|
41
|
+
|
|
42
|
+
Unlike DSH's built-in auto-compaction (which replaces a range with an automatically generated summary), billion-context-dsh:
|
|
43
|
+
|
|
44
|
+
- **Model-driven** — the model writes the summary itself; there is no second LLM summarization call (the ACP cost win)
|
|
45
|
+
- **Advisory, never imperative** — automatic policy only *nudges*; the model decides whether and when to compress
|
|
46
|
+
- **Durable & recoverable** — a compressed range becomes a checkpoint node, the originals stay in the append-only session log; `decompress` restores them, `search_context` finds information inside blocks
|
|
47
|
+
- **Seq-based refs** — no message tags; surface seqs are carried by the nudge's range table, with auto-balanced range edges and `#callId` tolerance
|
|
48
|
+
|
|
49
|
+
This is the DeepSeek Harness port of [billion-context-pi](https://github.com/ranxianglei/billion-context-pi) (the Pi coding-agent adapter): the compression core ([acp-kernel](https://github.com/ranxianglei/acp-kernel)) is reused verbatim, and the adapter layer was rewritten against DSH's durable-surface model — see [docs](https://github.com/Tyan66666/billion-context-dsh/tree/main/docs) for the verified mapping.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install billion-context-dsh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it. Add one composition row where a compaction backend is expected (host composition or an agent preset's `compaction` realm):
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
- id: compaction-billion-context
|
|
61
|
+
name: 'billion-context-dsh'
|
|
62
|
+
config:
|
|
63
|
+
modelContextLimit: 128000 # default; the pressure window
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **One context manager per agent.** The preset's `compaction` isolate realm should mount this engine *instead of* `dsh-compaction-basic` — two backends providing `ctx.compaction` would collide (see README mount examples in the [full docs](https://github.com/Tyan66666/billion-context-dsh/tree/main/docs)).
|
|
21
67
|
|
|
22
68
|
## How it works
|
|
23
69
|
|
|
@@ -27,49 +73,42 @@ DSH derives every model request from its append-only session log (the *surface*)
|
|
|
27
73
|
|---|---|
|
|
28
74
|
| `compress` tool shadows a range | durable `surfaceOp: { op: 'replace' }` — the model-written summary becomes a checkpoint node; the originals stay in the log |
|
|
29
75
|
| refs (`m00001` tags) | surface seqs, carried by the nudge's compressible-range table |
|
|
30
|
-
| nudge ("
|
|
76
|
+
| nudge ("consider compressing") | injected at `agent/pre-step` by the kernel's pressure decision — a short advisory, never an order |
|
|
31
77
|
| `decompress` | read-only recovery of shadowed originals from the log |
|
|
32
78
|
| `search_context` | scores block summaries + originals rebuilt from the log |
|
|
33
79
|
| `acp_status` | block ledger + context pressure |
|
|
34
80
|
| block state | in-memory kernel state + **log-rebuilt ledger** (no sidecar files) |
|
|
35
81
|
|
|
36
|
-
There is deliberately **no automatic summarization**: automatic policy only nudges the model (`compactIfNeeded` returns null).
|
|
82
|
+
The load-bearing compression guidance (tools, philosophy, summary rules) is registered as a one-time system-prompt section, so nudges stay short. There is deliberately **no automatic summarization**: automatic policy only nudges the model (`compactIfNeeded` returns null).
|
|
37
83
|
|
|
38
|
-
##
|
|
84
|
+
## Video
|
|
39
85
|
|
|
40
|
-
|
|
86
|
+
A walkthrough of the ACP philosophy this project inherits — how active context compression keeps a session lean at ~200K tokens (opencode-acp & billion-context-pi). *Video credit: the original author, [裘香莲](https://space.bilibili.com/) on Bilibili — not ours.*
|
|
41
87
|
|
|
42
|
-
|
|
43
|
-
# host composition (e.g. profile cordis.patch.yml)
|
|
44
|
-
- insert:
|
|
45
|
-
- id: compaction-billion-context
|
|
46
|
-
name: 'billion-context-dsh'
|
|
47
|
-
config:
|
|
48
|
-
modelContextLimit: 128000 # default; the pressure window
|
|
49
|
-
```
|
|
88
|
+
[](https://www.bilibili.com/video/BV1qAMR6MEA4/)
|
|
50
89
|
|
|
51
|
-
|
|
90
|
+
## Model-facing tools
|
|
52
91
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
92
|
+
| Tool | What it does |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `compress` | Replace a seq range with a dense summary you write (edges auto-balanced to tool-pair boundaries) |
|
|
95
|
+
| `decompress` | Restore a previously compressed block's original content (read-only) |
|
|
96
|
+
| `search_context` | Search compressed block summaries and originals by keyword |
|
|
97
|
+
| `acp_status` | Context usage, compressed blocks, compressible ranges |
|
|
98
|
+
| `/acp` | status / compress / decompress from the command bar |
|
|
99
|
+
|
|
100
|
+
## Upstream & credits
|
|
101
|
+
|
|
102
|
+
This project is a **port/derivation** and stands on the shoulders of the following upstream work — all MIT licensed. **Thank you** to [ranxianglei](https://github.com/ranxianglei) and the DeepSeek Harness team for building these projects and making them open source:
|
|
65
103
|
|
|
66
|
-
|
|
104
|
+
| Upstream | Author | Role |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| **[billion-context-pi](https://github.com/ranxianglei/billion-context-pi)** | [ranxianglei](https://github.com/ranxianglei) | The Pi coding-agent adapter this project ports to DeepSeek Harness; source of the adapter design, tool semantics, and this project's default configuration |
|
|
107
|
+
| **[acp-kernel](https://github.com/ranxianglei/acp-kernel)** | [ranxianglei](https://github.com/ranxianglei) | Framework-agnostic context-compression engine — reused **verbatim** (refs, blocks, tiers, nudge decisions, search, status) |
|
|
108
|
+
| **[opencode-acp](https://github.com/ranxianglei/opencode-acp)** | [ranxianglei](https://github.com/ranxianglei) | Origin of the ACP ("model decides when and what to compress") design |
|
|
109
|
+
| **[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** | DeepSeek AI | The host platform this project extends (compaction capability seam, agent presets, durable session log) |
|
|
67
110
|
|
|
68
|
-
- `
|
|
69
|
-
- `decompress` — recover a block's original content (read-only)
|
|
70
|
-
- `search_context` — find information inside compressed blocks
|
|
71
|
-
- `acp_status` — block ledger and pressure
|
|
72
|
-
- `/acp` — status / compress / decompress from the command bar
|
|
111
|
+
This project reuses `acp-kernel`'s compression core and `billion-context-pi`'s default behavior unchanged; the DSH adapter layer (session-event projection, durable surface transaction, model tools, nudge, config) is original work in this repository. Upstream copyright and licenses remain with their respective authors; see [LICENSE](LICENSE) for this project's terms.
|
|
73
112
|
|
|
74
113
|
## Configuration
|
|
75
114
|
|
|
@@ -99,13 +138,15 @@ npm run build # tsup bundle (inlines acp-kernel) + .d.ts
|
|
|
99
138
|
|
|
100
139
|
```
|
|
101
140
|
src/
|
|
102
|
-
├── index.ts
|
|
103
|
-
├── messages.ts
|
|
104
|
-
├── state.ts
|
|
105
|
-
├── region.ts
|
|
106
|
-
├── tools.ts
|
|
107
|
-
├── nudge.ts
|
|
108
|
-
|
|
141
|
+
├── index.ts # AcpCompactionEngine (CompactionEngine backend) + wiring
|
|
142
|
+
├── messages.ts # M1: session events ↔ acp-kernel CoreMessage projection
|
|
143
|
+
├── state.ts # M2: per-session kernel state
|
|
144
|
+
├── region.ts # M5: durable region transaction + log-rebuilt block ledger
|
|
145
|
+
├── tools.ts # M3: compress / decompress / search_context / acp_status
|
|
146
|
+
├── nudge.ts # M4: kernel pressure decision → injected advisory nudge
|
|
147
|
+
├── system-prompt.ts# M4: one-time ACP guidance section (keeps nudges short)
|
|
148
|
+
├── config.ts # kernel config assembly (thresholds + coreOverrides)
|
|
149
|
+
└── commands.ts # M4: /acp slash command
|
|
109
150
|
```
|
|
110
151
|
|
|
111
152
|
## License
|
package/README.zh-CN.md
CHANGED
|
@@ -2,24 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
[English](./README.md) | [中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **⚠️ 测试版声明——请勿用于生产环境**
|
|
6
|
+
> 本项目(**v0.1.1**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
|
|
10
|
+
<br />
|
|
11
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a> ·
|
|
12
|
+
<a href="https://github.com/ranxianglei/billion-context-pi">billion-context-pi</a> ·
|
|
13
|
+
<a href="https://github.com/ranxianglei/acp-kernel">acp-kernel</a> ·
|
|
14
|
+
<a href="https://github.com/ranxianglei/opencode-acp">opencode-acp</a>
|
|
15
|
+
</p>
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
<p align="center">
|
|
18
|
+
<strong>Billion-Context</strong> for <a href="https://github.com/deepseek-ai/deepseek-harness">DeepSeek Harness</a>
|
|
19
|
+
<br />
|
|
20
|
+
由模型决定<em>何时</em>压缩、<em>压缩什么</em>——而不是一个硬性上限。
|
|
21
|
+
</p>
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
---
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
<p align="center">
|
|
26
|
+
<a href="https://www.npmjs.com/package/billion-context-dsh"><img src="https://img.shields.io/npm/v/billion-context-dsh.svg?style=flat-square" alt="npm"></a>
|
|
27
|
+
<a href="https://github.com/Tyan66666/billion-context-dsh/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/billion-context-dsh.svg?style=flat-square" alt="license"></a>
|
|
28
|
+
<a href="https://github.com/Tyan66666/billion-context-dsh"><img src="https://img.shields.io/badge/GitHub-Tyan66666%2Fbillion--context--dsh-181717?style=flat-square&logo=github" alt="GitHub"></a>
|
|
29
|
+
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/topic-dsh--plugin-blue?style=flat-square" alt="dsh-plugin"></a>
|
|
30
|
+
</p>
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| **[acp-kernel](https://github.com/ranxianglei/acp-kernel)** | [ranxianglei](https://github.com/ranxianglei) | 框架无关的上下文压缩引擎——**原样复用**(refs、blocks、tiers、nudge 决策、search、status) |
|
|
19
|
-
| **[opencode-acp](https://github.com/ranxianglei/opencode-acp)** | [ranxianglei](https://github.com/ranxianglei) | ACP("模型决定何时压缩、压缩什么")设计的源头 |
|
|
20
|
-
| **[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** | DeepSeek AI | 本项目所扩展的宿主平台(compaction 能力接缝、agent preset、持久化会话日志) |
|
|
32
|
+
<p align="center">
|
|
33
|
+
<code>npm install billion-context-dsh</code>
|
|
34
|
+
</p>
|
|
21
35
|
|
|
22
|
-
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 为什么?
|
|
39
|
+
|
|
40
|
+
当对话变长,模型会耗尽上下文。多数工具直接硬截断——悄悄丢弃早期消息。**billion-context-dsh** 给模型一个 `compress` 工具:由 LLM 决定**何时**、**压缩什么**,写成高保真摘要,保留关键细节(文件路径、决策、错误信息)的同时回收上下文空间。
|
|
41
|
+
|
|
42
|
+
与 DSH 内置的自动压缩(用自动生成的摘要替换一段范围)不同,billion-context-dsh:
|
|
43
|
+
|
|
44
|
+
- **模型驱动** —— 摘要由模型自己书写,没有第二次 LLM 摘要调用(ACP 的成本优势)
|
|
45
|
+
- **只建议、不强令** —— 自动策略只 *nudge*(提醒),是否压缩、何时压缩由模型决定
|
|
46
|
+
- **持久且可恢复** —— 压缩范围成为 checkpoint 节点,原文保留在 append-only 会话日志中;`decompress` 可恢复,`search_context` 可在块内查找
|
|
47
|
+
- **基于 seq 引用** —— 不需要消息标签;surface seq 由 nudge 的范围表携带,范围边界自动平衡、容忍 `#callId` 片段
|
|
48
|
+
|
|
49
|
+
这是 [billion-context-pi](https://github.com/ranxianglei/billion-context-pi)(Pi 编码代理适配器)在 DeepSeek Harness 上的移植:压缩内核([acp-kernel](https://github.com/ranxianglei/acp-kernel))原样复用,适配层针对 DSH 的 durable-surface 模型重写——经过验证的映射关系见 [docs](https://github.com/Tyan66666/billion-context-dsh/tree/main/docs)。
|
|
50
|
+
|
|
51
|
+
## 安装
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install billion-context-dsh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
就这样。在需要压缩后端的位置加一行组合配置(宿主组合或 agent preset 的 `compaction` realm):
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
- id: compaction-billion-context
|
|
61
|
+
name: 'billion-context-dsh'
|
|
62
|
+
config:
|
|
63
|
+
modelContextLimit: 128000 # 默认;压力窗口
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **每个 agent 只留一个上下文管理器。** preset 的 `compaction` isolate realm 应该用本引擎*替换* `dsh-compaction-basic`——两个后端同时 provide `ctx.compaction` 会冲突(完整文档见 [docs](https://github.com/Tyan66666/billion-context-dsh/tree/main/docs))。
|
|
23
67
|
|
|
24
68
|
## 工作原理
|
|
25
69
|
|
|
@@ -28,50 +72,43 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
|
|
|
28
72
|
| ACP 概念 | DSH 实现 |
|
|
29
73
|
|---|---|
|
|
30
74
|
| `compress` 工具遮蔽一段范围 | 持久化 `surfaceOp: { op: 'replace' }`——模型书写的摘要成为 checkpoint 节点;原文保留在日志中 |
|
|
31
|
-
| refs(`m00001` 标签) |
|
|
32
|
-
| nudge("
|
|
75
|
+
| refs(`m00001` 标签) | surface seq,由 nudge 的可压缩范围表携带 |
|
|
76
|
+
| nudge("考虑压缩一下") | 由内核的压力决策在 `agent/pre-step` 注入——简短建议,绝非命令 |
|
|
33
77
|
| `decompress` | 从日志只读恢复被遮蔽的原文 |
|
|
34
78
|
| `search_context` | 对从日志重建的块摘要与原文打分 |
|
|
35
79
|
| `acp_status` | 块账本与上下文压力 |
|
|
36
80
|
| 块状态 | 内存内核状态 + **日志重建账本**(无旁车文件) |
|
|
37
81
|
|
|
38
|
-
|
|
82
|
+
承载性的压缩指引(工具、哲学、摘要规则)注册为一次性系统提示段,因此 nudge 保持简短。刻意**不做自动摘要**:自动策略只 nudge 模型(`compactIfNeeded` 返回 null)。
|
|
39
83
|
|
|
40
|
-
##
|
|
84
|
+
## 视频讲解
|
|
41
85
|
|
|
42
|
-
|
|
86
|
+
本项目继承的 ACP 哲学讲解——主动上下文压缩如何在约 20 万 token 内保持会话精简(opencode-acp 与 billion-context-pi)。*视频原作者:[裘香莲](https://space.bilibili.com/)(B 站 UP 主),非本项目制作。*
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
# 宿主组合(例如 profile 的 cordis.patch.yml)
|
|
46
|
-
- insert:
|
|
47
|
-
- id: compaction-billion-context
|
|
48
|
-
name: 'billion-context-dsh'
|
|
49
|
-
config:
|
|
50
|
-
modelContextLimit: 128000 # 默认;压力窗口
|
|
51
|
-
```
|
|
88
|
+
[](https://www.bilibili.com/video/BV1qAMR6MEA4/)
|
|
52
89
|
|
|
53
|
-
|
|
90
|
+
## 模型工具
|
|
54
91
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```
|
|
92
|
+
| 工具 | 作用 |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `compress` | 用你书写的紧凑摘要替换 seq 范围(边界自动平衡到 tool-call/result 配对点) |
|
|
95
|
+
| `decompress` | 恢复已压缩块的原始内容(只读) |
|
|
96
|
+
| `search_context` | 按关键词搜索压缩块摘要与原文 |
|
|
97
|
+
| `acp_status` | 上下文占用、压缩块、可压缩范围 |
|
|
98
|
+
| `/acp` | 从命令栏执行 status / compress / decompress |
|
|
99
|
+
|
|
100
|
+
## 上游项目与致谢
|
|
101
|
+
|
|
102
|
+
本项目是一个**移植/派生项目**,站在以下上游工作的肩膀上——全部为 MIT 许可。**衷心感谢** [ranxianglei](https://github.com/ranxianglei) 和 DeepSeek Harness 团队创建并开源这些项目:
|
|
67
103
|
|
|
68
|
-
|
|
104
|
+
| 上游项目 | 作者 | 角色 |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| **[billion-context-pi](https://github.com/ranxianglei/billion-context-pi)** | [ranxianglei](https://github.com/ranxianglei) | 本项目移植的 Pi 编码代理适配器;适配器设计、工具语义与本项目默认配置的来源 |
|
|
107
|
+
| **[acp-kernel](https://github.com/ranxianglei/acp-kernel)** | [ranxianglei](https://github.com/ranxianglei) | 框架无关的上下文压缩引擎——**原样复用**(refs、blocks、tiers、nudge 决策、search、status) |
|
|
108
|
+
| **[opencode-acp](https://github.com/ranxianglei/opencode-acp)** | [ranxianglei](https://github.com/ranxianglei) | ACP("模型决定何时压缩、压缩什么")设计的源头 |
|
|
109
|
+
| **[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** | DeepSeek AI | 本项目所扩展的宿主平台(compaction 能力接缝、agent preset、持久化会话日志) |
|
|
69
110
|
|
|
70
|
-
- `
|
|
71
|
-
- `decompress` — 恢复某块的原始内容(只读)
|
|
72
|
-
- `search_context` — 在压缩块内查找信息
|
|
73
|
-
- `acp_status` — 块账本与压力
|
|
74
|
-
- `/acp` — 从命令栏执行 status / compress / decompress
|
|
111
|
+
本项目原样复用 `acp-kernel` 的压缩内核与 `billion-context-pi` 的默认行为;DSH 适配层(会话事件投影、持久化表面事务、模型工具、nudge、配置)为本仓库原创。上游版权与许可归其各自作者所有;本项目的许可条款见 [LICENSE](LICENSE)。
|
|
75
112
|
|
|
76
113
|
## 配置
|
|
77
114
|
|
|
@@ -101,14 +138,15 @@ npm run build # tsup 打包(内联 acp-kernel)+ .d.ts
|
|
|
101
138
|
|
|
102
139
|
```
|
|
103
140
|
src/
|
|
104
|
-
├── index.ts
|
|
105
|
-
├── messages.ts
|
|
106
|
-
├── state.ts
|
|
107
|
-
├── region.ts
|
|
108
|
-
├── tools.ts
|
|
109
|
-
├── nudge.ts
|
|
110
|
-
├── system-prompt.ts
|
|
111
|
-
|
|
141
|
+
├── index.ts # AcpCompactionEngine(CompactionEngine 后端)+ 接线
|
|
142
|
+
├── messages.ts # M1: 会话事件 ↔ acp-kernel CoreMessage 投影
|
|
143
|
+
├── state.ts # M2: 每会话内核状态
|
|
144
|
+
├── region.ts # M5: 持久化区域事务 + 日志重建块账本
|
|
145
|
+
├── tools.ts # M3: compress / decompress / search_context / acp_status
|
|
146
|
+
├── nudge.ts # M4: 内核压力决策 → 注入的建议式 nudge
|
|
147
|
+
├── system-prompt.ts# M4: 一次性 ACP 指引段(让 nudge 保持简短)
|
|
148
|
+
├── config.ts # 内核配置组装(阈值 + coreOverrides)
|
|
149
|
+
└── commands.ts # M4: /acp 斜杠命令
|
|
112
150
|
```
|
|
113
151
|
|
|
114
152
|
## License
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var AcpStateStore = class {
|
|
|
28
28
|
|
|
29
29
|
// src/tools.ts
|
|
30
30
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
31
|
-
import {
|
|
31
|
+
import { defaultCountTokens as defaultCountTokens2 } from "acp-kernel";
|
|
32
32
|
|
|
33
33
|
// src/config.ts
|
|
34
34
|
import { defaultConfig } from "acp-kernel";
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
toolPairingBalancedBefore
|
|
54
54
|
} from "@deepseek-ai/dsh-compaction";
|
|
55
55
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
56
|
+
import { defaultCountTokens } from "acp-kernel";
|
|
56
57
|
|
|
57
58
|
// src/messages.ts
|
|
58
59
|
function extractText(content) {
|
|
@@ -178,26 +179,39 @@ function assertNoActiveCompaction(events) {
|
|
|
178
179
|
}
|
|
179
180
|
function resolveSurfaceRange(session, start, end) {
|
|
180
181
|
const nodes = session.surface.nodes;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
182
|
+
const requestedStartIdx = nodes.indexOf(start);
|
|
183
|
+
const requestedEndIdx = nodes.indexOf(end);
|
|
184
|
+
if (requestedStartIdx < 0 || requestedEndIdx < 0) {
|
|
184
185
|
throw new Error(`billion-context-dsh: seq ${start}..${end} not in the current surface`);
|
|
185
186
|
}
|
|
186
|
-
if (
|
|
187
|
+
if (requestedStartIdx > requestedEndIdx) {
|
|
187
188
|
throw new Error(`billion-context-dsh: reversed range ${start}..${end}`);
|
|
188
189
|
}
|
|
190
|
+
let startIdx = requestedStartIdx;
|
|
191
|
+
let endIdx = requestedEndIdx;
|
|
189
192
|
while (startIdx <= endIdx && !toolPairingBalancedBefore(session, nodes[startIdx])) {
|
|
190
193
|
startIdx += 1;
|
|
191
194
|
}
|
|
192
195
|
while (endIdx >= startIdx && !toolPairingBalancedAfter(session, nodes[endIdx])) {
|
|
193
196
|
endIdx -= 1;
|
|
194
197
|
}
|
|
195
|
-
if (startIdx
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
if (startIdx <= endIdx) {
|
|
199
|
+
return { start: nodes[startIdx], end: nodes[endIdx] };
|
|
200
|
+
}
|
|
201
|
+
startIdx = requestedStartIdx;
|
|
202
|
+
endIdx = requestedEndIdx;
|
|
203
|
+
while (startIdx > 0 && !toolPairingBalancedBefore(session, nodes[startIdx])) {
|
|
204
|
+
startIdx -= 1;
|
|
205
|
+
}
|
|
206
|
+
while (endIdx < nodes.length - 1 && !toolPairingBalancedAfter(session, nodes[endIdx])) {
|
|
207
|
+
endIdx += 1;
|
|
199
208
|
}
|
|
200
|
-
|
|
209
|
+
if (toolPairingBalancedBefore(session, nodes[startIdx]) && toolPairingBalancedAfter(session, nodes[endIdx])) {
|
|
210
|
+
return { start: nodes[startIdx], end: nodes[endIdx] };
|
|
211
|
+
}
|
|
212
|
+
throw new Error(
|
|
213
|
+
`billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} \u2014 narrow the range or consult acp_status for the current surface`
|
|
214
|
+
);
|
|
201
215
|
}
|
|
202
216
|
function shadowedSeqsOf(session, start, end) {
|
|
203
217
|
const nodes = session.surface.nodes;
|
|
@@ -236,17 +250,73 @@ function rebuildBlockLedger(events) {
|
|
|
236
250
|
for (const event of events) {
|
|
237
251
|
if (event.type !== "compaction/summary") continue;
|
|
238
252
|
const data = event.data;
|
|
253
|
+
let shadowedTokenCount = data.shadowedTokenCount;
|
|
254
|
+
if (shadowedTokenCount === 0) {
|
|
255
|
+
shadowedTokenCount = 0;
|
|
256
|
+
for (const seq of data.shadowedSeqs) {
|
|
257
|
+
const original = events[seq];
|
|
258
|
+
if (original !== void 0) shadowedTokenCount += defaultCountTokens(extractEventText(original));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
239
261
|
ledger.push({
|
|
240
262
|
blockId: data.compactionId,
|
|
241
263
|
summary: extractText(data.summary),
|
|
242
264
|
shadowedSeqs: [...data.shadowedSeqs],
|
|
243
|
-
shadowedTokenCount
|
|
265
|
+
shadowedTokenCount,
|
|
244
266
|
start: data.shadowedRange.start,
|
|
245
267
|
end: data.shadowedRange.end
|
|
246
268
|
});
|
|
247
269
|
}
|
|
248
270
|
return ledger;
|
|
249
271
|
}
|
|
272
|
+
function isCheckpointNode(event) {
|
|
273
|
+
if (event.type !== "user/message") return false;
|
|
274
|
+
const source = event.data.source;
|
|
275
|
+
return source?.plugin === "compact";
|
|
276
|
+
}
|
|
277
|
+
function buildCompressibleSeqRanges(session, opts = {}) {
|
|
278
|
+
const nodes = session.surface.nodes;
|
|
279
|
+
const preserve = opts.preserveRecent ?? 5;
|
|
280
|
+
const protectedSeqs = /* @__PURE__ */ new Set();
|
|
281
|
+
for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq);
|
|
282
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
283
|
+
const event = session.events[nodes[index]];
|
|
284
|
+
if (event?.type === "user/message" && !isCheckpointNode(event)) {
|
|
285
|
+
protectedSeqs.add(nodes[index]);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const raw = [];
|
|
290
|
+
let cur = null;
|
|
291
|
+
const flush = () => {
|
|
292
|
+
if (cur !== null) raw.push(cur);
|
|
293
|
+
cur = null;
|
|
294
|
+
};
|
|
295
|
+
for (const seq of nodes) {
|
|
296
|
+
const event = session.events[seq];
|
|
297
|
+
if (event === void 0 || protectedSeqs.has(seq) || isCheckpointNode(event)) {
|
|
298
|
+
flush();
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const tokens = defaultCountTokens(extractEventText(event));
|
|
302
|
+
if (cur === null) {
|
|
303
|
+
cur = { start: seq, end: seq, count: 1, tokens };
|
|
304
|
+
} else {
|
|
305
|
+
cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
flush();
|
|
309
|
+
const out = [];
|
|
310
|
+
for (const range of raw) {
|
|
311
|
+
try {
|
|
312
|
+
const { start, end } = resolveSurfaceRange(session, range.start, range.end);
|
|
313
|
+
const count = range.count;
|
|
314
|
+
out.push({ start, end, count, tokens: range.tokens });
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return out.sort((a, b) => b.tokens - a.tokens);
|
|
319
|
+
}
|
|
250
320
|
|
|
251
321
|
// src/tools.ts
|
|
252
322
|
function textOutput() {
|
|
@@ -306,7 +376,7 @@ async function handleCompress(env, args, exec) {
|
|
|
306
376
|
const session = agent.session;
|
|
307
377
|
const state = env.store.stateFor(session);
|
|
308
378
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
309
|
-
const tokenCount = coreMessages.reduce((sum, message) => sum +
|
|
379
|
+
const tokenCount = coreMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
310
380
|
const config = kernelConfigFor(env);
|
|
311
381
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
312
382
|
env.store.set(session, turn.state);
|
|
@@ -346,12 +416,17 @@ async function handleCompress(env, args, exec) {
|
|
|
346
416
|
const original = args.content[index];
|
|
347
417
|
const { start, end } = resolveSurfaceRange(session, range.startSeq, range.endSeq);
|
|
348
418
|
const shadowed = shadowedSeqsOf(session, start, end);
|
|
419
|
+
let shadowedTokens = 0;
|
|
420
|
+
for (const seq of shadowed) {
|
|
421
|
+
const event = session.events[seq];
|
|
422
|
+
if (event !== void 0) shadowedTokens += defaultCountTokens2(extractEventText(event));
|
|
423
|
+
}
|
|
349
424
|
const { compactionId } = runCompactionTransaction(session, {
|
|
350
425
|
start,
|
|
351
426
|
end,
|
|
352
427
|
shadowedSeqs: shadowed,
|
|
353
428
|
summary: [{ type: "text", text: original.summary }],
|
|
354
|
-
shadowedTokenCount:
|
|
429
|
+
shadowedTokenCount: shadowedTokens,
|
|
355
430
|
provider: agent.options.provider ?? "",
|
|
356
431
|
model: agent.options.model ?? ""
|
|
357
432
|
});
|
|
@@ -418,7 +493,7 @@ function handleStatus(env, _args, exec) {
|
|
|
418
493
|
const ledger = rebuildBlockLedger(session.events);
|
|
419
494
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
420
495
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
421
|
-
const estimated = coreMessages.reduce((sum, message) => sum +
|
|
496
|
+
const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
422
497
|
const limit = env.modelContextLimit;
|
|
423
498
|
const lines = [
|
|
424
499
|
`ACP status \u2014 session ${session.id}`,
|
|
@@ -473,13 +548,13 @@ function makeTools(env) {
|
|
|
473
548
|
}
|
|
474
549
|
|
|
475
550
|
// src/commands.ts
|
|
476
|
-
import {
|
|
551
|
+
import { defaultCountTokens as defaultCountTokens3 } from "acp-kernel";
|
|
477
552
|
function statusText(env, agent) {
|
|
478
553
|
const session = agent.session;
|
|
479
554
|
const ledger = rebuildBlockLedger(session.events);
|
|
480
555
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
481
556
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
482
|
-
const estimated = coreMessages.reduce((sum, message) => sum +
|
|
557
|
+
const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens3(message.text ?? ""), 0);
|
|
483
558
|
const limit = env.modelContextLimit;
|
|
484
559
|
const lines = [
|
|
485
560
|
`ACP status \u2014 session ${session.id}`,
|
|
@@ -505,12 +580,17 @@ function compressText(env, agent, args) {
|
|
|
505
580
|
const session = agent.session;
|
|
506
581
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
507
582
|
const shadowed = shadowedSeqsOf(session, startSeq, endSeq);
|
|
583
|
+
let shadowedTokens = 0;
|
|
584
|
+
for (const seq of shadowed) {
|
|
585
|
+
const event = session.events[seq];
|
|
586
|
+
if (event !== void 0) shadowedTokens += defaultCountTokens3(extractEventText(event));
|
|
587
|
+
}
|
|
508
588
|
const { compactionId } = runCompactionTransaction(session, {
|
|
509
589
|
start,
|
|
510
590
|
end,
|
|
511
591
|
shadowedSeqs: shadowed,
|
|
512
592
|
summary: [{ type: "text", text: summary }],
|
|
513
|
-
shadowedTokenCount:
|
|
593
|
+
shadowedTokenCount: shadowedTokens,
|
|
514
594
|
provider: agent.options.provider ?? "",
|
|
515
595
|
model: agent.options.model ?? ""
|
|
516
596
|
});
|
|
@@ -549,31 +629,31 @@ function acpCommand(env) {
|
|
|
549
629
|
|
|
550
630
|
// src/nudge.ts
|
|
551
631
|
import {
|
|
552
|
-
|
|
632
|
+
defaultCountTokens as defaultCountTokens4
|
|
553
633
|
} from "acp-kernel";
|
|
554
634
|
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
555
|
-
function rangeTable(
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const endRaw = byRef[range.endRef];
|
|
560
|
-
if (startRaw === void 0 || endRaw === void 0) return null;
|
|
561
|
-
return ` - seq ${startRaw}..${endRaw} \u2014 ${range.count} messages, ~${range.tokens} tokens`;
|
|
562
|
-
});
|
|
563
|
-
const visible = lines.filter((line) => line !== null);
|
|
564
|
-
if (visible.length === 0) return "";
|
|
635
|
+
function rangeTable(session) {
|
|
636
|
+
const ranges = buildCompressibleSeqRanges(session).slice(0, 6);
|
|
637
|
+
if (ranges.length === 0) return "";
|
|
638
|
+
const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} \u2014 ${range.count} messages, ~${range.tokens} tokens`);
|
|
565
639
|
return [
|
|
566
640
|
"",
|
|
567
641
|
"Compressible ranges (refs are surface seqs):",
|
|
568
|
-
...
|
|
642
|
+
...lines,
|
|
569
643
|
"Compress with: compress({ content: [{ startSeq, endSeq, summary }] })"
|
|
570
644
|
].join("\n");
|
|
571
645
|
}
|
|
646
|
+
function measuredTokenCount(agent, coreMessages) {
|
|
647
|
+
const meter = agent.ctx?.get?.("tokenMeter");
|
|
648
|
+
const surface = meter?.measure?.(agent.session)?.surfaceTokens;
|
|
649
|
+
if (typeof surface === "number" && surface > 0) return surface;
|
|
650
|
+
return coreMessages.reduce((sum, message) => sum + defaultCountTokens4(message.text ?? ""), 0);
|
|
651
|
+
}
|
|
572
652
|
function buildNudge(agent, env, lastNudgeTurn) {
|
|
573
653
|
const session = agent.session;
|
|
574
654
|
const state = env.store.stateFor(session);
|
|
575
655
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
576
|
-
const tokenCount =
|
|
656
|
+
const tokenCount = measuredTokenCount(agent, coreMessages);
|
|
577
657
|
const config = kernelConfigFor(env);
|
|
578
658
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
579
659
|
env.store.set(session, turn.state);
|
|
@@ -584,18 +664,18 @@ function buildNudge(agent, env, lastNudgeTurn) {
|
|
|
584
664
|
const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber;
|
|
585
665
|
if (alreadyShown) return null;
|
|
586
666
|
lastNudgeTurn.set(session.id, turnNumber);
|
|
587
|
-
const text = buildNudgeText(nudge, emergency,
|
|
667
|
+
const text = buildNudgeText(nudge, emergency, session);
|
|
588
668
|
const message = createUserMessage2({
|
|
589
669
|
content: [{ type: "text", text }],
|
|
590
670
|
source: { kind: "plugin", plugin: "acp-nudge" }
|
|
591
671
|
});
|
|
592
672
|
return { message, emergency };
|
|
593
673
|
}
|
|
594
|
-
function buildNudgeText(nudge, emergency,
|
|
595
|
-
const pct = Math.round(nudge.contextUsage * 100);
|
|
674
|
+
function buildNudgeText(nudge, emergency, session) {
|
|
675
|
+
const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100);
|
|
596
676
|
const frame = emergency ? `\u26A0\uFE0F Context usage is at ${pct}% of the window \u2014 nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.` : `Context usage is at ${pct}%. This is a suggestion, not a requirement \u2014 you decide whether and when to compress.`;
|
|
597
677
|
const guidance = "Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.";
|
|
598
|
-
return [frame, "", guidance, rangeTable(
|
|
678
|
+
return [frame, "", guidance, rangeTable(session)].join("\n");
|
|
599
679
|
}
|
|
600
680
|
|
|
601
681
|
// src/system-prompt.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/state.ts","../src/tools.ts","../src/config.ts","../src/region.ts","../src/messages.ts","../src/commands.ts","../src/nudge.ts","../src/system-prompt.ts"],"sourcesContent":["/**\n * billion-context-dsh — Active Context Pruning (ACP) for the DeepSeek Harness,\n * delivered as a `CompactionEngine` backend.\n *\n * The model decides when and what to compress (pure ACP semantics):\n * - the `compress` tool durably shadows a surface range with the model-written\n * summary (no second LLM summarization call — the ACP cost win);\n * - the original events stay in the append-only session log, so `decompress`,\n * `search_context`, and replay always work;\n * - refs are surface seqs carried by the injected nudge's range table (DSH\n * has no in-memory message rewrite hook — see docs/dsh-porting-verification.md);\n * - automatic policy never summarizes by itself: it nudges the model.\n *\n * Mount it wherever a compaction backend is expected:\n *\n * ```yaml\n * - id: compaction-billion-context\n * name: 'billion-context-dsh'\n * config:\n * modelContextLimit: 128000\n * ```\n *\n * The package registers `ctx.compaction` plus the four model tools and the\n * `/acp` command when the hosting composition provides `ctx.tools` /\n * `ctx.commands`.\n * @module billion-context-dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport {\n CompactionEngine,\n ManualCompactionError,\n type CompactionAgentContext,\n type CompactionResult,\n type CompactionTrigger,\n type ManualCompactAgentContext,\n} from '@deepseek-ai/dsh-compaction'\nimport { createCore, type CompressionCore } from 'acp-kernel'\nimport type {} from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { makeTools, type ToolEnvironment } from './tools.ts'\nimport { acpCommand } from './commands.ts'\nimport { buildNudge } from './nudge.ts'\nimport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\n\nexport { AcpStateStore } from './state.ts'\nexport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nexport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nexport { makeTools, type ToolEnvironment } from './tools.ts'\nexport { acpCommand } from './commands.ts'\nexport { buildNudge, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts'\nexport {\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n findOpenTurn,\n assertNoActiveCompaction,\n type AcpBlockLedgerEntry,\n type CompactionTransactionInput,\n} from './region.ts'\nexport { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts'\n\nexport interface AcpConfig {\n /** The context window used for pressure decisions. Default 128000. */\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction). Kernel default 0.45 — same as billion-context-pi. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound (over-limit). Kernel default 0.75 — same as billion-context-pi. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default 0.95 — same as billion-context-pi. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (billion-context-pi's `coreOverrides` escape hatch). */\n readonly coreOverrides?: Partial<import('acp-kernel').Config>\n /** Register the four model tools on `ctx.tools`. Default true. */\n readonly autoTools: boolean\n /** Register the `/acp` command on `ctx.commands`. Default true. */\n readonly autoCommand: boolean\n /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */\n readonly autoNudge: boolean\n}\n\nconst DEFAULT_CONFIG: AcpConfig = {\n modelContextLimit: 128000,\n autoTools: true,\n autoCommand: true,\n autoNudge: true,\n}\n\nexport function resolveAcpConfig(config: Partial<AcpConfig> = {}): AcpConfig {\n return { ...DEFAULT_CONFIG, ...config }\n}\n\n/**\n * The ACP compaction backend. Subclasses the seam exactly like\n * `dsh-compaction-basic`; swaps summarization-driven compaction for\n * model-driven block compression without touching the agent loop.\n */\nexport class AcpCompactionEngine extends CompactionEngine {\n /** The framework-agnostic ACP compression core, reused verbatim. */\n readonly kernel: CompressionCore\n /** Per-session kernel state. */\n readonly store: AcpStateStore\n /** Resolved engine configuration. */\n readonly config: AcpConfig\n\n private readonly lastNudgeTurn = new Map<string, number>()\n\n constructor(ctx: Context, config: Partial<AcpConfig> = {}) {\n super(ctx)\n this.config = resolveAcpConfig(config)\n this.kernel = createCore({})\n this.store = new AcpStateStore()\n\n const env: ToolEnvironment = {\n kernel: this.kernel,\n store: this.store,\n modelContextLimit: this.config.modelContextLimit,\n nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,\n nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,\n nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,\n coreOverrides: this.config.coreOverrides,\n }\n\n if (this.config.autoTools) {\n const tools = ctx.get('tools')\n if (tools !== undefined) {\n for (const tool of makeTools(env)) tools.register(tool)\n }\n }\n if (this.config.autoCommand) {\n const commands = ctx.get('commands')\n if (commands !== undefined) commands.register(acpCommand(env))\n }\n if (this.config.autoNudge) {\n ctx.on('agent/pre-step', async (payload, next) => {\n const decision = await next()\n if (decision.kind === 'reject') return decision\n const outcome = buildNudge(payload.agent, env, this.lastNudgeTurn)\n if (outcome === null) return decision\n return { kind: 'enter', messages: [...decision.messages, outcome.message] }\n })\n }\n // The load-bearing ACP guidance lives in the system prompt ONCE; nudges\n // stay short and advisory (model-driven: the model decides).\n const systemPrompt = ctx.get('systemPrompt')\n if (systemPrompt !== undefined) {\n systemPrompt.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: ACP_SYSTEM_PROMPT,\n })\n }\n }\n\n /** ACP is model-driven: automatic pressure policy never summarizes by itself. */\n override async compactIfNeeded(\n _agent: CompactionAgentContext,\n _trigger: CompactionTrigger,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /** Explicit idle-session compaction: ACP leaves the decision to the model. */\n override async compactNow(\n _agent: ManualCompactAgentContext,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /**\n * The model-driven path lands through the `compress` tool, which runs the\n * full durable transaction directly. This seam method rejects with guidance:\n * automatic summarization is exactly what ACP replaces.\n */\n override async compactRegion(\n _start: number,\n _end: number,\n _agent: CompactionAgentContext,\n signal?: AbortSignal,\n ): Promise<CompactionResult> {\n signal?.throwIfAborted()\n throw new ManualCompactionError(\n 'summary',\n 'billion-context-dsh is model-driven: use the compress tool instead of automatic summarization',\n )\n }\n}\n\nexport default AcpCompactionEngine\n","/**\n * M2 — per-session ACP kernel state.\n *\n * The in-memory map holds the exact acp-kernel `CompressionState` while a\n * session is live. Durability does not rely on a sidecar file: every durable\n * compression writes a `compaction/summary` event whose shadowed range and\n * summary re-derive the block ledger (`rebuildBlockLedger` in region.ts), so a\n * restarted engine can answer decompress/search/status from the session log\n * alone — DSH's \"log is the source of truth\" model.\n * @module billion-context-dsh/state\n */\n\nimport type { Session } from '@deepseek-ai/dsh-session'\nimport { createInitialState, type CompressionState } from 'acp-kernel'\n\nexport class AcpStateStore {\n private readonly states = new Map<string, CompressionState>()\n\n /** Kernel state for one session, initialised on first access. */\n stateFor(session: Session): CompressionState {\n const id = session.id\n const existing = this.states.get(id)\n if (existing !== undefined) return existing\n const state = createInitialState()\n this.states.set(id, state)\n return state\n }\n\n set(session: Session, state: CompressionState): void {\n this.states.set(session.id, state)\n }\n\n delete(session: Session): void {\n this.states.delete(session.id)\n }\n}\n","/**\n * M3 — the four model tools: compress / decompress / search_context /\n * acp_status, registered through `ctx.tools` (defineTool).\n *\n * compress is the heart of ACP: the model writes the summary and the tool\n * lands it as a durable surface replacement (no second LLM summarization\n * call). decompress recovers shadowed content read-only from the log (DSH\n * keeps the originals — V5). search_context scores blocks rebuilt from the\n * log. acp_status reports the block ledger and pressure.\n * @module billion-context-dsh/tools\n */\n\nimport { defineTool, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'\nimport { estimateTokensFast, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { AcpStateStore } from './state.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport {\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\n\nexport interface ToolEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n}\n\ninterface TextOutput {\n text: string\n}\n\nfunction textOutput(): {\n schema: { type: 'object'; properties: { text: { type: 'string' } }; additionalProperties: boolean }\n render: (args: unknown, value: TextOutput) => import('@deepseek-ai/dsh-llm').ContentBlock[]\n} {\n return {\n schema: {\n type: 'object',\n properties: { text: { type: 'string' } },\n additionalProperties: false,\n },\n render: (_args, value) => [{ type: 'text', text: value.text }],\n }\n}\n\nfunction requireAgent(exec: ToolRunContext): Agent {\n if (exec.agent === undefined) {\n throw new Error('billion-context-dsh: tool requires an agent execution context')\n }\n return exec.agent\n}\n\nconst compressParameters = {\n topic: { type: 'string' as const, description: 'Fallback topic for entries without their own.' },\n content: {\n type: 'array' as const,\n required: true,\n description: 'One or more ranges to compress, each with startSeq/endSeq boundaries (surface seqs) and a dense summary.',\n items: {\n type: 'object' as const,\n properties: {\n startSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'First surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n endSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'Inclusive last surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n summary: { type: 'string' as const, description: 'Complete technical summary replacing the range; keep paths, decisions, values verbatim. Minimum 50 characters.' },\n topic: { type: 'string' as const, description: 'Short label (3-5 words) for this range.' },\n },\n additionalProperties: false,\n },\n },\n} as const\n\n/** Normalize a seq arg: number, \"295\", or \"295#call_00_xxx\" → 295. */\nfunction parseSeq(value: number | string): number {\n const text = String(value).split('#')[0]!.trim()\n const seq = Number(text)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(`billion-context-dsh: invalid seq \"${String(value)}\" — use a surface seq like 295`)\n }\n return seq\n}\n\ninterface CompressArgs {\n topic?: string\n content: Array<{ startSeq: number | string; endSeq: number | string; summary: string; topic?: string }>\n}\n\n/** Resolve seq → kernel ref, then applyCompression and land the transaction. */\nasync function handleCompress(env: ToolEnvironment, args: CompressArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n const state = env.store.stateFor(session)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = coreMessages.reduce((sum, message) => sum + estimateTokensFast(message.text ?? ''), 0)\n const config = kernelConfigFor(env)\n\n // Assign refs / advance state exactly like a turn would.\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n const byRaw = turn.state.messageRefs.byRaw\n\n const ranges = args.content.map((range) => {\n const startSeq = parseSeq(range.startSeq)\n const endSeq = parseSeq(range.endSeq)\n const startRef = byRaw[String(startSeq)]\n const endRef = byRaw[String(endSeq)]\n if (startRef === undefined || endRef === undefined) {\n throw new Error(\n `billion-context-dsh: seq ${startSeq}..${endSeq} has no assigned ref — `\n + 'the range must be on the current surface (run acp_status for the live seq list)',\n )\n }\n return {\n startSeq,\n endSeq,\n startRef,\n endRef,\n summary: range.summary,\n ...(range.topic ?? args.topic) === undefined ? {} : { topic: range.topic ?? args.topic },\n }\n })\n\n const applied = env.kernel.applyCompression({\n ranges: ranges.map(({ startRef, endRef, summary, topic }) => ({ startRef, endRef, summary, topic })),\n messages: coreMessages,\n state: turn.state,\n config,\n })\n if (applied.result.errors.length > 0) {\n return { text: `compress failed: ${applied.result.errors.join('; ')}` }\n }\n env.store.set(session, applied.state)\n\n const lines: string[] = []\n for (let index = 0; index < ranges.length; index += 1) {\n const range = ranges[index]!\n const original = args.content[index]!\n // The edges may have been nudged to the nearest tool-pairing-balanced cut;\n // shadow exactly what resolveSurfaceRange approved.\n const { start, end } = resolveSurfaceRange(session, range.startSeq, range.endSeq)\n const shadowed = shadowedSeqsOf(session, start, end)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: original.summary }],\n shadowedTokenCount: 0,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n const adjusted = start !== range.startSeq || end !== range.endSeq\n lines.push(\n ` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed`\n + (adjusted ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)` : ''),\n )\n }\n\n return {\n text: `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.\\n${lines.join('\\n')}`,\n }\n}\n\nconst decompressParameters = {\n blockId: { type: 'string' as const, required: true, description: 'Block id from acp_status or search_context (the compaction id).' },\n} as const\n\ninterface DecompressArgs {\n blockId: string\n}\n\nfunction handleDecompress(_env: ToolEnvironment, args: DecompressArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args.blockId))\n if (block === undefined) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const parts: string[] = []\n for (const seq of block.shadowedSeqs) {\n const event = session.events[seq]\n const text = event === undefined ? '' : extractEventText(event)\n if (text.length > 0) parts.push(`[seq ${seq}] ${text}`)\n }\n return {\n text: `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`,\n }\n}\n\nconst searchParameters = {\n query: { type: 'string' as const, required: true, description: 'Search terms to find inside compressed blocks.' },\n limit: { type: 'integer' as const, description: 'Maximum results (default 5).' },\n} as const\n\ninterface SearchArgs {\n query: string\n limit?: number\n}\n\nfunction handleSearch(_env: ToolEnvironment, args: SearchArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const terms = args.query.toLowerCase().split(/\\s+/).filter(Boolean)\n const scored: Array<{ blockId: string; score: number; summary: string }> = []\n for (const block of ledger) {\n const original = block.shadowedSeqs\n .map((seq) => extractEventText(session.events[seq]!))\n .join('\\n')\n const haystack = `${block.summary}\\n${original}`.toLowerCase()\n let score = 0\n for (const term of terms) score += haystack.split(term).length - 1\n if (score > 0) scored.push({ blockId: block.blockId, score, summary: block.summary })\n }\n scored.sort((a, b) => b.score - a.score)\n const top = scored.slice(0, args.limit ?? 5)\n if (top.length === 0) return { text: `search_context: no matches for \"${args.query}\"` }\n return {\n text: `Matches for \"${args.query}\":\\n`\n + top.map((hit) => ` - ${hit.blockId} (score ${hit.score}): ${hit.summary.slice(0, 160)}`).join('\\n')\n + '\\n\\nDecompress with: decompress({ blockId })',\n }\n}\n\nconst statusParameters = {} as const\n\ninterface StatusArgs {\n [key: string]: never\n}\n\nfunction handleStatus(env: ToolEnvironment, _args: StatusArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = coreMessages.reduce((sum, message) => sum + estimateTokensFast(message.text ?? ''), 0)\n const limit = env.modelContextLimit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ]\n for (const block of ledger.slice(0, 10)) {\n lines.push(` - ${block.blockId.slice(0, 8)}: seqs ${block.start}..${block.end} (${block.shadowedSeqs.length} msgs) — ${block.summary.slice(0, 80)}`)\n }\n return { text: lines.join('\\n') }\n}\n\n/** Build the four ACP model tools bound to one engine. */\nexport function makeTools(env: ToolEnvironment): ToolDefinition[] {\n return [\n defineTool({\n name: 'compress',\n description:\n 'Replace older conversation ranges with dense summaries you write. '\n + 'Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). '\n + 'Batch multiple unrelated ranges in one call. Never compress content the current step is actively using.',\n parameters: compressParameters,\n output: textOutput(),\n async execute(args, exec) {\n return handleCompress(env, args as CompressArgs, exec)\n },\n }),\n defineTool({\n name: 'decompress',\n description: 'Recover the original content of a compressed block by its blockId (read-only; does not unshadow the range).',\n parameters: decompressParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleDecompress(env, args as DecompressArgs, exec))\n },\n }),\n defineTool({\n name: 'search_context',\n description: 'Search inside compressed blocks (summaries and original content) for information the model no longer sees in context.',\n parameters: searchParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleSearch(env, args as SearchArgs, exec))\n },\n }),\n defineTool({\n name: 'acp_status',\n description: 'Report the ACP block ledger: compressed blocks, reclaimed tokens, and current context pressure.',\n parameters: statusParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleStatus(env, args as StatusArgs, exec))\n },\n }),\n ]\n}\n","/**\n * Kernel configuration assembly — the DSH counterpart of billion-context-pi's\n * `resolveConfig`: build acp-kernel's `Config` from adapter-level knobs.\n *\n * Defaults are deliberately the acp-kernel `defaultConfig` values (the same\n * defaults billion-context-pi ships: nudge window 45%–75%, emergency 95%,\n * growth ratio 5%, protected last messages 5). Every knob is optional — an\n * omitted value keeps the kernel default, so the behavior matches the Pi\n * adapter exactly unless a deployment opts out.\n * @module billion-context-dsh/config\n */\n\nimport { defaultConfig, type Config } from 'acp-kernel'\n\n/** The kernel-facing knobs shared by the nudge path and the compress tool. */\nexport interface KernelConfigInput {\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction). Kernel default: 0.45. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound — over-limit. Kernel default: 0.75. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default: 0.95. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (the billion-context-pi escape hatch). */\n readonly coreOverrides?: Partial<Config>\n}\n\n/**\n * Assemble the kernel config: `defaultConfig(limit)` merged with the optional\n * nudge thresholds (merged into the defaults, never replacing them wholesale)\n * and any additional `coreOverrides`.\n */\nexport function kernelConfigFor(input: KernelConfigInput): Config {\n const nudgePatch: Partial<Config['nudge']> = {}\n if (input.nudgeMinContextLimitPct !== undefined) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct\n if (input.nudgeMaxContextLimitPct !== undefined) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct\n if (input.nudgeEmergencyThresholdPct !== undefined) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct\n\n const overrides: Partial<Config> = { ...input.coreOverrides }\n if (Object.keys(nudgePatch).length > 0) {\n overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch }\n }\n return defaultConfig(input.modelContextLimit, overrides)\n}\n","/**\n * M5 — durable region transaction and the log-rebuilt block ledger.\n *\n * Modeled on `dsh-compaction-basic/src/region.ts` (which is package-internal\n * and not exported by the seam): validate the surface range and tool-call/result\n * pairing, take the durable `compaction/start` lock, record `compaction/summary`\n * as the shadow price, land the `user/message` surface replacement carrying the\n * summary under `compactCheckpointSource`, and release the lock with\n * `compaction/end`. The original events stay in the append-only log, so\n * decompress/search/status can rebuild everything from the log.\n * @module billion-context-dsh/region\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport {\n CompactionId,\n compactCheckpointSource,\n toolPairingBalancedAfter,\n toolPairingBalancedBefore,\n} from '@deepseek-ai/dsh-compaction'\nimport { createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'\nimport { extractText } from './messages.ts'\n\n/** One durable ACP block as rebuilt from the session log. */\nexport interface AcpBlockLedgerEntry {\n /** The compaction transaction id (stable block identity). */\n readonly blockId: string\n readonly summary: string\n readonly shadowedSeqs: readonly number[]\n readonly shadowedTokenCount: number\n readonly start: number\n readonly end: number\n}\n\n/** The open turn number, or null when the log ends between turns. */\nexport function findOpenTurn(events: readonly SessionEvent[]): number | null {\n let open: number | null = null\n for (const event of events) {\n if (event.type === 'turn/start') open = event.data.turn\n else if (event.type === 'turn/end' && event.data.turn === open) open = null\n }\n return open\n}\n\n/** Reject a second concurrent compaction for the same session. */\nexport function assertNoActiveCompaction(events: readonly SessionEvent[]): void {\n let active = false\n for (const event of events) {\n if (event.type === 'compaction/start') active = true\n else if (event.type === 'compaction/end') active = false\n }\n if (active) {\n throw new Error('billion-context-dsh: another compaction is already active for this session')\n }\n}\n\n/**\n * Validate one inclusive surface span and adjust its edges to the nearest\n * tool-pairing-balanced cuts. Missing or reversed ranges still throw; an\n * edge that sits inside a tool-call/result pair is nudged outward to the\n * closest clean cut instead (models routinely pick edges from the nudge's\n * range table that straddle a pair). The returned range is what a caller\n * should actually shadow.\n */\nexport function resolveSurfaceRange(\n session: Session,\n start: number,\n end: number,\n): { start: number; end: number } {\n const nodes = session.surface.nodes\n let startIdx = nodes.indexOf(start)\n let endIdx = nodes.indexOf(end)\n if (startIdx < 0 || endIdx < 0) {\n throw new Error(`billion-context-dsh: seq ${start}..${end} not in the current surface`)\n }\n if (startIdx > endIdx) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // Nudge the start forward and the end backward to the nearest balanced cuts.\n while (startIdx <= endIdx && !toolPairingBalancedBefore(session, nodes[startIdx]!)) {\n startIdx += 1\n }\n while (endIdx >= startIdx && !toolPairingBalancedAfter(session, nodes[endIdx]!)) {\n endIdx -= 1\n }\n if (startIdx > endIdx) {\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced range inside seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n }\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n}\n\n/** The surface seqs shadowed by the inclusive positional span. */\nexport function shadowedSeqsOf(session: Session, start: number, end: number): number[] {\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(start)\n const endIdx = nodes.indexOf(end)\n return nodes.slice(startIdx, endIdx + 1)\n}\n\nexport interface CompactionTransactionInput {\n readonly start: number\n readonly end: number\n readonly shadowedSeqs: readonly number[]\n readonly summary: ContentBlock[]\n readonly shadowedTokenCount: number\n readonly provider: string\n readonly model: string\n}\n\n/**\n * Run one durable compression transaction. Throws on invalid state; on success\n * the four events are in the log and the surface has one summary node.\n */\nexport function runCompactionTransaction(\n session: Session,\n input: CompactionTransactionInput,\n): { compactionId: string; seqs: number[] } {\n assertNoActiveCompaction(session.events)\n const turn = findOpenTurn(session.events)\n const compactionId = CompactionId(randomUUID())\n const seqs: number[] = []\n\n seqs.push(session.append('compaction/start', { compactionId, turn }).seq)\n seqs.push(session.append('compaction/summary', {\n compactionId,\n summary: input.summary,\n shadowedRange: { start: input.start, end: input.end },\n shadowedSeqs: [...input.shadowedSeqs],\n shadowedTokenCount: input.shadowedTokenCount,\n provider: input.provider,\n model: input.model,\n }).seq)\n\n const message = createUserMessage({\n content: input.summary,\n source: compactCheckpointSource(compactionId),\n })\n seqs.push(session.append('user/message', message, {\n surfaceOp: { op: 'replace', start: input.start, end: input.end },\n sourceEventSeqs: [...input.shadowedSeqs],\n }).seq)\n\n seqs.push(session.append('compaction/end', { compactionId, turn }).seq)\n return { compactionId, seqs }\n}\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const ledger: AcpBlockLedgerEntry[] = []\n for (const event of events) {\n if (event.type !== 'compaction/summary') continue\n const data = event.data\n ledger.push({\n blockId: data.compactionId,\n summary: extractText(data.summary),\n shadowedSeqs: [...data.shadowedSeqs],\n shadowedTokenCount: data.shadowedTokenCount,\n start: data.shadowedRange.start,\n end: data.shadowedRange.end,\n })\n }\n return ledger\n}\n","/**\n * M1 — session-log projection: DSH surface events → acp-kernel CoreMessage.\n *\n * The ACP kernel is message-array based; DSH is event-log based. This module\n * is the bridge in the direction the engine needs (projectEvent /\n * eventsToCoreMessages). The reverse direction (CoreMessage[] → session\n * appends) is the M5 region transaction's job.\n * Mirrors billion-context-pi's `projectMessage`/`entriesToCoreMessages`\n * against DSH event shapes (see V-verification: SurfaceEventType =\n * 'user/message' | 'assistant/message' | 'tool/result').\n * @module billion-context-pi-dsh/messages\n */\n\nimport type { CoreMessage } from 'acp-kernel'\nimport type { SessionEvent } from '@deepseek-ai/dsh-session'\n\n/**\n * Extract plain text from a DSH content block array or string.\n *\n * Recursive: a real DSH `tool-result` block is `{ type: 'tool-result',\n * toolCallId, content: ContentBlock[] }` — the inner `content` array holds\n * the actual `text` blocks, so a top-level-only walk would drop every tool\n * result from the projection (and with it the seq's ref assignment, breaking\n * compress boundary resolution). Nested arrays are flattened depth-first.\n */\nexport function extractText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown; content?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text)\n } else if (Array.isArray(b.content)) {\n parts.push(extractText(b.content))\n }\n }\n return parts.join('\\n')\n}\n\ninterface ToolCallBlock {\n type: 'tool-call'\n id?: string\n name?: string\n arguments?: unknown\n}\n\nfunction toolCallsOf(content: unknown): ToolCallBlock[] {\n if (!Array.isArray(content)) return []\n return content.filter((b): b is ToolCallBlock => (b as { type?: string }).type === 'tool-call')\n}\n\nfunction stringifyArgs(args: unknown): string {\n if (!args) return ''\n if (typeof args === 'string') return args\n try {\n return JSON.stringify(args)\n } catch {\n return String(args)\n }\n}\n\n/**\n * Project one surface message event into CoreMessage(s).\n * - user/message → user text (verbatim content)\n * - assistant/message → assistant text, or one CoreMessage per tool-call\n * - tool/result → tool result (toolName/toolCallId, role 'tool')\n * Non-surface events project to nothing.\n */\nexport function projectEvent(event: SessionEvent): CoreMessage[] {\n switch (event.type) {\n case 'user/message': {\n const text = extractText((event.data as { content?: unknown }).content)\n return text.length > 0 ? [{ id: String(event.seq), role: 'user', contentType: 'text', text }] : []\n }\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = toolCallsOf(content)\n const text = extractText(content)\n if (calls.length === 0) {\n return text.trim().length > 0\n ? [{ id: String(event.seq), role: 'assistant', contentType: 'text', text }]\n : []\n }\n if (calls.length === 1) {\n const call = calls[0]!\n const argStr = stringifyArgs(call.arguments)\n const body = argStr && text ? `${text}\\n${argStr}` : argStr || text\n return [{\n id: String(event.seq),\n role: 'assistant',\n contentType: 'tool-call',\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: body,\n }]\n }\n return calls.map((call) => ({\n id: `${event.seq}#${call.id ?? ''}`,\n role: 'assistant' as const,\n contentType: 'tool-call' as const,\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: stringifyArgs(call.arguments) || text,\n }))\n }\n case 'tool/result': {\n const message = (event.data as {\n message?: { content?: unknown; toolName?: string; toolCallId?: string }\n }).message\n const text = extractText(message?.content)\n if (text.length === 0) return []\n return [{\n id: String(event.seq),\n role: 'tool',\n contentType: 'tool-result',\n toolName: message?.toolName ?? '',\n toolCallId: message?.toolCallId ?? '',\n text,\n }]\n }\n default:\n return []\n }\n}\n\n/** Project a session's message events into CoreMessage[] in log order. */\nexport function eventsToCoreMessages(events: readonly SessionEvent[]): CoreMessage[] {\n const out: CoreMessage[] = []\n for (const event of events) out.push(...projectEvent(event))\n return out\n}\n\n/** The surface-visible message events of a session, in model-visible order. */\nexport function surfaceEventsOf(session: import('@deepseek-ai/dsh-session').Session): SessionEvent[] {\n return session.surface.nodes\n .map((seq) => session.events[seq])\n .filter((event): event is SessionEvent => event !== undefined)\n}\n\n/** Extract the model-facing text of any surface message event. */\nexport function extractEventText(event: SessionEvent): string {\n switch (event.type) {\n case 'user/message':\n return extractText((event.data as { content?: unknown }).content)\n case 'assistant/message':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n case 'tool/result':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n default:\n return ''\n }\n}\n","/**\n * M4 — the `/acp` slash command: a human-friendly window into the same\n * machinery the model tools expose (status, one-shot compress, decompress).\n * @module billion-context-dsh/commands\n */\n\nimport type { CommandDefinition } from '@deepseek-ai/dsh-commands'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { ToolEnvironment } from './tools.ts'\nimport {\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { defaultConfig, estimateTokensFast } from 'acp-kernel'\n\nfunction statusText(env: ToolEnvironment, agent: Agent): string {\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = coreMessages.reduce((sum, message) => sum + estimateTokensFast(message.text ?? ''), 0)\n const limit = env.modelContextLimit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ]\n for (const block of ledger.slice(0, 10)) {\n lines.push(` - ${block.blockId.slice(0, 8)}: seqs ${block.start}..${block.end} — ${block.summary.slice(0, 80)}`)\n }\n return lines.join('\\n')\n}\n\nfunction compressText(env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 3) {\n return '/acp compress <startSeq> <endSeq> <summary...>'\n }\n const startSeq = Number(args[0])\n const endSeq = Number(args[1])\n const summary = args.slice(2).join(' ')\n if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {\n return '/acp compress: startSeq and endSeq must be integers'\n }\n const session = agent.session\n const { start, end } = resolveSurfaceRange(session, startSeq, endSeq)\n const shadowed = shadowedSeqsOf(session, startSeq, endSeq)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: summary }],\n shadowedTokenCount: 0,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`\n}\n\nfunction decompressText(_env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 1) return '/acp decompress <blockId>'\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args[0]!))\n if (block === undefined) return `block \"${args[0]}\" not found (see /acp status)`\n const parts = block.shadowedSeqs\n .map((seq) => extractEventText(session.events[seq]!))\n .filter((text) => text.length > 0)\n return `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`\n}\n\n/** Register the /acp command (idempotent per engine). */\nexport function acpCommand(env: ToolEnvironment): CommandDefinition {\n return {\n name: 'acp',\n description:\n 'Active Context Pruning — model-driven context compression. '\n + 'Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId>',\n handler: async (invocation) => {\n const raw = invocation.rawInput.trim()\n if (raw === '' || raw === 'status') {\n return { kind: 'success', text: statusText(env, invocation.agent) }\n }\n if (raw.startsWith('compress')) {\n return { kind: 'success', text: compressText(env, invocation.agent, raw.slice('compress'.length).trim().split(/\\s+/) ) }\n }\n if (raw.startsWith('decompress')) {\n return { kind: 'success', text: decompressText(env, invocation.agent, raw.slice('decompress'.length).trim().split(/\\s+/)) }\n }\n return { kind: 'error', text: `unknown /acp subcommand \"${raw.split(/\\s+/)[0]}\" — use status | compress | decompress` }\n },\n }\n}\n","/**\n * M4 — ACP nudge: the kernel's compression recommendation, rendered as an\n * injected user message with a seq-based compressible-range table (D1:\n * \"seq is the ref\" — DSH has no in-memory message rewrite hook, so the model\n * targets ranges by surface seq rather than by <acp> tags).\n * @module billion-context-dsh/nudge\n */\n\nimport {\n estimateTokensFast,\n type CompressionCore,\n type NudgeDecision,\n} from 'acp-kernel'\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { eventsToCoreMessages, surfaceEventsOf } from './messages.ts'\nimport { findOpenTurn } from './region.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\n\n/** Kernel inputs the nudge path shares with the compress tool. */\nexport interface NudgeEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n}\n\nexport interface NudgeOutcome {\n readonly message: UserMessage\n readonly emergency: boolean\n}\n\n/** Render the compressible-range table as seq refs for the model. */\nfunction rangeTable(nudge: NudgeDecision, state: { messageRefs: { byRef: Record<string, string> } }): string {\n const byRef = state.messageRefs.byRef\n const lines = nudge.compressibleRanges.slice(0, 6).map((range) => {\n const startRaw = byRef[range.startRef]\n const endRaw = byRef[range.endRef]\n if (startRaw === undefined || endRaw === undefined) return null\n return ` - seq ${startRaw}..${endRaw} — ${range.count} messages, ~${range.tokens} tokens`\n })\n const visible = lines.filter((line): line is string => line !== null)\n if (visible.length === 0) return ''\n return [\n '',\n 'Compressible ranges (refs are surface seqs):',\n ...visible,\n 'Compress with: compress({ content: [{ startSeq, endSeq, summary }] })',\n ].join('\\n')\n}\n\n/**\n * Decide and build one nudge message for the agent's next pre-step. Returns\n * null when the kernel recommends no nudge or one was already injected for the\n * current turn (emergency nudges always bypass the dedup). Also advances the\n * in-memory kernel state (ref assignment) so the compress tool can resolve\n * seq → mNNNNN refs.\n */\nexport function buildNudge(\n agent: Agent,\n env: NudgeEnvironment,\n lastNudgeTurn: Map<string, number>,\n): NudgeOutcome | null {\n const session = agent.session\n const state = env.store.stateFor(session)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = coreMessages.reduce((sum, message) => sum + estimateTokensFast(message.text ?? ''), 0)\n const config = kernelConfigFor(env)\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n\n const nudge = turn.nudge\n if (nudge === undefined || !nudge.shouldInject) return null\n const emergency = nudge.breakdown?.emergencyOverride === 1\n\n const turnNumber = findOpenTurn(session.events) ?? 0\n const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber\n if (alreadyShown) return null\n lastNudgeTurn.set(session.id, turnNumber)\n\n const text = buildNudgeText(nudge, emergency, turn.state)\n const message = createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'acp-nudge' },\n })\n return { message, emergency }\n}\n\n/**\n * A short ADVISORY nudge — ACP is model-driven, the model decides whether and\n * when to compress. Full guidance (tools, philosophy, summary rules) lives in\n * the system prompt once, not in every nudge.\n */\nfunction buildNudgeText(\n nudge: NudgeDecision,\n emergency: boolean,\n state: { messageRefs: { byRef: Record<string, string> } },\n): string {\n const pct = Math.round(nudge.contextUsage * 100)\n const frame = emergency\n ? `⚠️ Context usage is at ${pct}% of the window — nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.`\n : `Context usage is at ${pct}%. This is a suggestion, not a requirement — you decide whether and when to compress.`\n const guidance = 'Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.'\n return [frame, '', guidance, rangeTable(nudge, state)].join('\\n')\n}\n","/**\n * M4 — the ACP system-prompt section (DSH counterpart of billion-context-pi's\n * ACP_SYSTEM_PROMPT): the load-bearing compression guidance lives here, ONCE,\n * instead of being re-sent with every nudge. The nudge itself stays a short,\n * advisory notice — ACP is model-driven, the model decides whether and when\n * to compress (never \"compress now\").\n * @module billion-context-dsh/system-prompt\n */\n\nimport { COMPRESS_PHILOSOPHY } from 'acp-kernel'\n\nexport const ACP_SYSTEM_PROMPT = `Active Context Pruning — model-driven context management\n\nYOU decide whether and when to compress context. Nothing forces you: the injected \"nudge\" is a suggestion, not an order, and you may ignore it when compression would not help. Compress only ranges you have genuinely consumed (read tool outputs, finished explorations, superseded steps) that the current work no longer needs verbatim.\n\n${COMPRESS_PHILOSOPHY}\n\nCompression tools (refs are SURFACE SEQS, not ids):\n- compress: replace a seq range with your dense self-contained summary. compress({ content: [{ startSeq, endSeq, summary }] }). Edges are auto-balanced to tool-call/result boundaries; a trailing #callId fragment in a seq is ignored. Ranges must be on the current surface — stale seqs fail with guidance.\n- decompress: recover a compressed block's original content, read-only. decompress({ blockId }).\n- search_context: find information inside compressed blocks BEFORE decompressing. search_context({ query }).\n- acp_status: current context usage and the live compressible-range list. Run it before compressing when in doubt.\n\nWhen you write a summary, it becomes the ONLY record of that range: keep file paths, signatures, exact values, decisions, and error strings verbatim so a later reader (or you, after decompress) can continue without the original. Never reuse historical seqs — the surface moves as messages land and compress; verify with acp_status.`\n\n/** System-prompt section order: tool guidance lives in 100–199. */\nexport const ACP_SYSTEM_PROMPT_ORDER = 150\n"],"mappings":"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","names":["estimateTokensFast","estimateTokensFast","createUserMessage","message","estimateTokensFast","createUserMessage"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/state.ts","../src/tools.ts","../src/config.ts","../src/region.ts","../src/messages.ts","../src/commands.ts","../src/nudge.ts","../src/system-prompt.ts"],"sourcesContent":["/**\n * billion-context-dsh — Active Context Pruning (ACP) for the DeepSeek Harness,\n * delivered as a `CompactionEngine` backend.\n *\n * The model decides when and what to compress (pure ACP semantics):\n * - the `compress` tool durably shadows a surface range with the model-written\n * summary (no second LLM summarization call — the ACP cost win);\n * - the original events stay in the append-only session log, so `decompress`,\n * `search_context`, and replay always work;\n * - refs are surface seqs carried by the injected nudge's range table (DSH\n * has no in-memory message rewrite hook — see docs/dsh-porting-verification.md);\n * - automatic policy never summarizes by itself: it nudges the model.\n *\n * Mount it wherever a compaction backend is expected:\n *\n * ```yaml\n * - id: compaction-billion-context\n * name: 'billion-context-dsh'\n * config:\n * modelContextLimit: 128000\n * ```\n *\n * The package registers `ctx.compaction` plus the four model tools and the\n * `/acp` command when the hosting composition provides `ctx.tools` /\n * `ctx.commands`.\n * @module billion-context-dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport {\n CompactionEngine,\n ManualCompactionError,\n type CompactionAgentContext,\n type CompactionResult,\n type CompactionTrigger,\n type ManualCompactAgentContext,\n} from '@deepseek-ai/dsh-compaction'\nimport { createCore, type CompressionCore } from 'acp-kernel'\nimport type {} from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { makeTools, type ToolEnvironment } from './tools.ts'\nimport { acpCommand } from './commands.ts'\nimport { buildNudge } from './nudge.ts'\nimport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\n\nexport { AcpStateStore } from './state.ts'\nexport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nexport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nexport { makeTools, type ToolEnvironment } from './tools.ts'\nexport { acpCommand } from './commands.ts'\nexport { buildNudge, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts'\nexport {\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n findOpenTurn,\n assertNoActiveCompaction,\n type AcpBlockLedgerEntry,\n type CompactionTransactionInput,\n} from './region.ts'\nexport { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts'\n\nexport interface AcpConfig {\n /** The context window used for pressure decisions. Default 128000. */\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction). Kernel default 0.45 — same as billion-context-pi. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound (over-limit). Kernel default 0.75 — same as billion-context-pi. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default 0.95 — same as billion-context-pi. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (billion-context-pi's `coreOverrides` escape hatch). */\n readonly coreOverrides?: Partial<import('acp-kernel').Config>\n /** Register the four model tools on `ctx.tools`. Default true. */\n readonly autoTools: boolean\n /** Register the `/acp` command on `ctx.commands`. Default true. */\n readonly autoCommand: boolean\n /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */\n readonly autoNudge: boolean\n}\n\nconst DEFAULT_CONFIG: AcpConfig = {\n modelContextLimit: 128000,\n autoTools: true,\n autoCommand: true,\n autoNudge: true,\n}\n\nexport function resolveAcpConfig(config: Partial<AcpConfig> = {}): AcpConfig {\n return { ...DEFAULT_CONFIG, ...config }\n}\n\n/**\n * The ACP compaction backend. Subclasses the seam exactly like\n * `dsh-compaction-basic`; swaps summarization-driven compaction for\n * model-driven block compression without touching the agent loop.\n */\nexport class AcpCompactionEngine extends CompactionEngine {\n /** The framework-agnostic ACP compression core, reused verbatim. */\n readonly kernel: CompressionCore\n /** Per-session kernel state. */\n readonly store: AcpStateStore\n /** Resolved engine configuration. */\n readonly config: AcpConfig\n\n private readonly lastNudgeTurn = new Map<string, number>()\n\n constructor(ctx: Context, config: Partial<AcpConfig> = {}) {\n super(ctx)\n this.config = resolveAcpConfig(config)\n this.kernel = createCore({})\n this.store = new AcpStateStore()\n\n const env: ToolEnvironment = {\n kernel: this.kernel,\n store: this.store,\n modelContextLimit: this.config.modelContextLimit,\n nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,\n nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,\n nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,\n coreOverrides: this.config.coreOverrides,\n }\n\n if (this.config.autoTools) {\n const tools = ctx.get('tools')\n if (tools !== undefined) {\n for (const tool of makeTools(env)) tools.register(tool)\n }\n }\n if (this.config.autoCommand) {\n const commands = ctx.get('commands')\n if (commands !== undefined) commands.register(acpCommand(env))\n }\n if (this.config.autoNudge) {\n ctx.on('agent/pre-step', async (payload, next) => {\n const decision = await next()\n if (decision.kind === 'reject') return decision\n const outcome = buildNudge(payload.agent, env, this.lastNudgeTurn)\n if (outcome === null) return decision\n return { kind: 'enter', messages: [...decision.messages, outcome.message] }\n })\n }\n // The load-bearing ACP guidance lives in the system prompt ONCE; nudges\n // stay short and advisory (model-driven: the model decides).\n const systemPrompt = ctx.get('systemPrompt')\n if (systemPrompt !== undefined) {\n systemPrompt.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: ACP_SYSTEM_PROMPT,\n })\n }\n }\n\n /** ACP is model-driven: automatic pressure policy never summarizes by itself. */\n override async compactIfNeeded(\n _agent: CompactionAgentContext,\n _trigger: CompactionTrigger,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /** Explicit idle-session compaction: ACP leaves the decision to the model. */\n override async compactNow(\n _agent: ManualCompactAgentContext,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /**\n * The model-driven path lands through the `compress` tool, which runs the\n * full durable transaction directly. This seam method rejects with guidance:\n * automatic summarization is exactly what ACP replaces.\n */\n override async compactRegion(\n _start: number,\n _end: number,\n _agent: CompactionAgentContext,\n signal?: AbortSignal,\n ): Promise<CompactionResult> {\n signal?.throwIfAborted()\n throw new ManualCompactionError(\n 'summary',\n 'billion-context-dsh is model-driven: use the compress tool instead of automatic summarization',\n )\n }\n}\n\nexport default AcpCompactionEngine\n","/**\n * M2 — per-session ACP kernel state.\n *\n * The in-memory map holds the exact acp-kernel `CompressionState` while a\n * session is live. Durability does not rely on a sidecar file: every durable\n * compression writes a `compaction/summary` event whose shadowed range and\n * summary re-derive the block ledger (`rebuildBlockLedger` in region.ts), so a\n * restarted engine can answer decompress/search/status from the session log\n * alone — DSH's \"log is the source of truth\" model.\n * @module billion-context-dsh/state\n */\n\nimport type { Session } from '@deepseek-ai/dsh-session'\nimport { createInitialState, type CompressionState } from 'acp-kernel'\n\nexport class AcpStateStore {\n private readonly states = new Map<string, CompressionState>()\n\n /** Kernel state for one session, initialised on first access. */\n stateFor(session: Session): CompressionState {\n const id = session.id\n const existing = this.states.get(id)\n if (existing !== undefined) return existing\n const state = createInitialState()\n this.states.set(id, state)\n return state\n }\n\n set(session: Session, state: CompressionState): void {\n this.states.set(session.id, state)\n }\n\n delete(session: Session): void {\n this.states.delete(session.id)\n }\n}\n","/**\n * M3 — the four model tools: compress / decompress / search_context /\n * acp_status, registered through `ctx.tools` (defineTool).\n *\n * compress is the heart of ACP: the model writes the summary and the tool\n * lands it as a durable surface replacement (no second LLM summarization\n * call). decompress recovers shadowed content read-only from the log (DSH\n * keeps the originals — V5). search_context scores blocks rebuilt from the\n * log. acp_status reports the block ledger and pressure.\n * @module billion-context-dsh/tools\n */\n\nimport { defineTool, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'\nimport { defaultCountTokens, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { AcpStateStore } from './state.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport {\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\n\nexport interface ToolEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n}\n\ninterface TextOutput {\n text: string\n}\n\nfunction textOutput(): {\n schema: { type: 'object'; properties: { text: { type: 'string' } }; additionalProperties: boolean }\n render: (args: unknown, value: TextOutput) => import('@deepseek-ai/dsh-llm').ContentBlock[]\n} {\n return {\n schema: {\n type: 'object',\n properties: { text: { type: 'string' } },\n additionalProperties: false,\n },\n render: (_args, value) => [{ type: 'text', text: value.text }],\n }\n}\n\nfunction requireAgent(exec: ToolRunContext): Agent {\n if (exec.agent === undefined) {\n throw new Error('billion-context-dsh: tool requires an agent execution context')\n }\n return exec.agent\n}\n\nconst compressParameters = {\n topic: { type: 'string' as const, description: 'Fallback topic for entries without their own.' },\n content: {\n type: 'array' as const,\n required: true,\n description: 'One or more ranges to compress, each with startSeq/endSeq boundaries (surface seqs) and a dense summary.',\n items: {\n type: 'object' as const,\n properties: {\n startSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'First surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n endSeq: {\n oneOf: [\n { type: 'integer' as const, description: 'Inclusive last surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n summary: { type: 'string' as const, description: 'Complete technical summary replacing the range; keep paths, decisions, values verbatim. Minimum 50 characters.' },\n topic: { type: 'string' as const, description: 'Short label (3-5 words) for this range.' },\n },\n additionalProperties: false,\n },\n },\n} as const\n\n/** Normalize a seq arg: number, \"295\", or \"295#call_00_xxx\" → 295. */\nfunction parseSeq(value: number | string): number {\n const text = String(value).split('#')[0]!.trim()\n const seq = Number(text)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(`billion-context-dsh: invalid seq \"${String(value)}\" — use a surface seq like 295`)\n }\n return seq\n}\n\ninterface CompressArgs {\n topic?: string\n content: Array<{ startSeq: number | string; endSeq: number | string; summary: string; topic?: string }>\n}\n\n/** Resolve seq → kernel ref, then applyCompression and land the transaction. */\nasync function handleCompress(env: ToolEnvironment, args: CompressArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n const state = env.store.stateFor(session)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n const config = kernelConfigFor(env)\n\n // Assign refs / advance state exactly like a turn would.\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n const byRaw = turn.state.messageRefs.byRaw\n\n const ranges = args.content.map((range) => {\n const startSeq = parseSeq(range.startSeq)\n const endSeq = parseSeq(range.endSeq)\n const startRef = byRaw[String(startSeq)]\n const endRef = byRaw[String(endSeq)]\n if (startRef === undefined || endRef === undefined) {\n throw new Error(\n `billion-context-dsh: seq ${startSeq}..${endSeq} has no assigned ref — `\n + 'the range must be on the current surface (run acp_status for the live seq list)',\n )\n }\n return {\n startSeq,\n endSeq,\n startRef,\n endRef,\n summary: range.summary,\n ...(range.topic ?? args.topic) === undefined ? {} : { topic: range.topic ?? args.topic },\n }\n })\n\n const applied = env.kernel.applyCompression({\n ranges: ranges.map(({ startRef, endRef, summary, topic }) => ({ startRef, endRef, summary, topic })),\n messages: coreMessages,\n state: turn.state,\n config,\n })\n if (applied.result.errors.length > 0) {\n return { text: `compress failed: ${applied.result.errors.join('; ')}` }\n }\n env.store.set(session, applied.state)\n\n const lines: string[] = []\n for (let index = 0; index < ranges.length; index += 1) {\n const range = ranges[index]!\n const original = args.content[index]!\n // The edges may have been nudged to the nearest tool-pairing-balanced cut;\n // shadow exactly what resolveSurfaceRange approved.\n const { start, end } = resolveSurfaceRange(session, range.startSeq, range.endSeq)\n const shadowed = shadowedSeqsOf(session, start, end)\n // Estimate the reclaimed tokens from the actual shadowed messages so the\n // durable ledger (compaction/summary.shadowedTokenCount) reports a real\n // number instead of 0.\n let shadowedTokens = 0\n for (const seq of shadowed) {\n const event = session.events[seq]\n if (event !== undefined) shadowedTokens += defaultCountTokens(extractEventText(event))\n }\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: original.summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n const adjusted = start !== range.startSeq || end !== range.endSeq\n lines.push(\n ` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed`\n + (adjusted ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)` : ''),\n )\n }\n\n return {\n text: `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.\\n${lines.join('\\n')}`,\n }\n}\n\nconst decompressParameters = {\n blockId: { type: 'string' as const, required: true, description: 'Block id from acp_status or search_context (the compaction id).' },\n} as const\n\ninterface DecompressArgs {\n blockId: string\n}\n\nfunction handleDecompress(_env: ToolEnvironment, args: DecompressArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args.blockId))\n if (block === undefined) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const parts: string[] = []\n for (const seq of block.shadowedSeqs) {\n const event = session.events[seq]\n const text = event === undefined ? '' : extractEventText(event)\n if (text.length > 0) parts.push(`[seq ${seq}] ${text}`)\n }\n return {\n text: `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`,\n }\n}\n\nconst searchParameters = {\n query: { type: 'string' as const, required: true, description: 'Search terms to find inside compressed blocks.' },\n limit: { type: 'integer' as const, description: 'Maximum results (default 5).' },\n} as const\n\ninterface SearchArgs {\n query: string\n limit?: number\n}\n\nfunction handleSearch(_env: ToolEnvironment, args: SearchArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const terms = args.query.toLowerCase().split(/\\s+/).filter(Boolean)\n const scored: Array<{ blockId: string; score: number; summary: string }> = []\n for (const block of ledger) {\n const original = block.shadowedSeqs\n .map((seq) => extractEventText(session.events[seq]!))\n .join('\\n')\n const haystack = `${block.summary}\\n${original}`.toLowerCase()\n let score = 0\n for (const term of terms) score += haystack.split(term).length - 1\n if (score > 0) scored.push({ blockId: block.blockId, score, summary: block.summary })\n }\n scored.sort((a, b) => b.score - a.score)\n const top = scored.slice(0, args.limit ?? 5)\n if (top.length === 0) return { text: `search_context: no matches for \"${args.query}\"` }\n return {\n text: `Matches for \"${args.query}\":\\n`\n + top.map((hit) => ` - ${hit.blockId} (score ${hit.score}): ${hit.summary.slice(0, 160)}`).join('\\n')\n + '\\n\\nDecompress with: decompress({ blockId })',\n }\n}\n\nconst statusParameters = {} as const\n\ninterface StatusArgs {\n [key: string]: never\n}\n\nfunction handleStatus(env: ToolEnvironment, _args: StatusArgs, exec: ToolRunContext): TextOutput {\n const session = requireAgent(exec).session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n const limit = env.modelContextLimit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ]\n for (const block of ledger.slice(0, 10)) {\n lines.push(` - ${block.blockId.slice(0, 8)}: seqs ${block.start}..${block.end} (${block.shadowedSeqs.length} msgs) — ${block.summary.slice(0, 80)}`)\n }\n return { text: lines.join('\\n') }\n}\n\n/** Build the four ACP model tools bound to one engine. */\nexport function makeTools(env: ToolEnvironment): ToolDefinition[] {\n return [\n defineTool({\n name: 'compress',\n description:\n 'Replace older conversation ranges with dense summaries you write. '\n + 'Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). '\n + 'Batch multiple unrelated ranges in one call. Never compress content the current step is actively using.',\n parameters: compressParameters,\n output: textOutput(),\n async execute(args, exec) {\n return handleCompress(env, args as CompressArgs, exec)\n },\n }),\n defineTool({\n name: 'decompress',\n description: 'Recover the original content of a compressed block by its blockId (read-only; does not unshadow the range).',\n parameters: decompressParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleDecompress(env, args as DecompressArgs, exec))\n },\n }),\n defineTool({\n name: 'search_context',\n description: 'Search inside compressed blocks (summaries and original content) for information the model no longer sees in context.',\n parameters: searchParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleSearch(env, args as SearchArgs, exec))\n },\n }),\n defineTool({\n name: 'acp_status',\n description: 'Report the ACP block ledger: compressed blocks, reclaimed tokens, and current context pressure.',\n parameters: statusParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleStatus(env, args as StatusArgs, exec))\n },\n }),\n ]\n}\n","/**\n * Kernel configuration assembly — the DSH counterpart of billion-context-pi's\n * `resolveConfig`: build acp-kernel's `Config` from adapter-level knobs.\n *\n * Defaults are deliberately the acp-kernel `defaultConfig` values (the same\n * defaults billion-context-pi ships: nudge window 45%–75%, emergency 95%,\n * growth ratio 5%, protected last messages 5). Every knob is optional — an\n * omitted value keeps the kernel default, so the behavior matches the Pi\n * adapter exactly unless a deployment opts out.\n * @module billion-context-dsh/config\n */\n\nimport { defaultConfig, type Config } from 'acp-kernel'\n\n/** The kernel-facing knobs shared by the nudge path and the compress tool. */\nexport interface KernelConfigInput {\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction). Kernel default: 0.45. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound — over-limit. Kernel default: 0.75. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default: 0.95. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (the billion-context-pi escape hatch). */\n readonly coreOverrides?: Partial<Config>\n}\n\n/**\n * Assemble the kernel config: `defaultConfig(limit)` merged with the optional\n * nudge thresholds (merged into the defaults, never replacing them wholesale)\n * and any additional `coreOverrides`.\n */\nexport function kernelConfigFor(input: KernelConfigInput): Config {\n const nudgePatch: Partial<Config['nudge']> = {}\n if (input.nudgeMinContextLimitPct !== undefined) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct\n if (input.nudgeMaxContextLimitPct !== undefined) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct\n if (input.nudgeEmergencyThresholdPct !== undefined) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct\n\n const overrides: Partial<Config> = { ...input.coreOverrides }\n if (Object.keys(nudgePatch).length > 0) {\n overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch }\n }\n return defaultConfig(input.modelContextLimit, overrides)\n}\n","/**\n * M5 — durable region transaction and the log-rebuilt block ledger.\n *\n * Modeled on `dsh-compaction-basic/src/region.ts` (which is package-internal\n * and not exported by the seam): validate the surface range and tool-call/result\n * pairing, take the durable `compaction/start` lock, record `compaction/summary`\n * as the shadow price, land the `user/message` surface replacement carrying the\n * summary under `compactCheckpointSource`, and release the lock with\n * `compaction/end`. The original events stay in the append-only log, so\n * decompress/search/status can rebuild everything from the log.\n * @module billion-context-dsh/region\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport {\n CompactionId,\n compactCheckpointSource,\n toolPairingBalancedAfter,\n toolPairingBalancedBefore,\n} from '@deepseek-ai/dsh-compaction'\nimport { createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'\nimport { defaultCountTokens } from 'acp-kernel'\nimport { extractEventText, extractText } from './messages.ts'\n\n/** One durable ACP block as rebuilt from the session log. */\nexport interface AcpBlockLedgerEntry {\n /** The compaction transaction id (stable block identity). */\n readonly blockId: string\n readonly summary: string\n readonly shadowedSeqs: readonly number[]\n readonly shadowedTokenCount: number\n readonly start: number\n readonly end: number\n}\n\n/** The open turn number, or null when the log ends between turns. */\nexport function findOpenTurn(events: readonly SessionEvent[]): number | null {\n let open: number | null = null\n for (const event of events) {\n if (event.type === 'turn/start') open = event.data.turn\n else if (event.type === 'turn/end' && event.data.turn === open) open = null\n }\n return open\n}\n\n/** Reject a second concurrent compaction for the same session. */\nexport function assertNoActiveCompaction(events: readonly SessionEvent[]): void {\n let active = false\n for (const event of events) {\n if (event.type === 'compaction/start') active = true\n else if (event.type === 'compaction/end') active = false\n }\n if (active) {\n throw new Error('billion-context-dsh: another compaction is already active for this session')\n }\n}\n\n/**\n * Validate one inclusive surface span and adjust its edges to a\n * tool-pairing-balanced range. Missing or reversed ranges still throw. An\n * edge that sits inside a tool-call/result pair is first nudged inward to the\n * nearest clean cut; if that collapses the range (e.g. the model asked for a\n * SINGLE tool result, which can never be balanced alone), the range EXPANDS\n * outward to the enclosing balanced pair instead — a lone tool message is\n * almost always a \"consumed output\" the model genuinely wants to compress.\n * The returned range is what a caller should actually shadow.\n */\nexport function resolveSurfaceRange(\n session: Session,\n start: number,\n end: number,\n): { start: number; end: number } {\n const nodes = session.surface.nodes\n const requestedStartIdx = nodes.indexOf(start)\n const requestedEndIdx = nodes.indexOf(end)\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n throw new Error(`billion-context-dsh: seq ${start}..${end} not in the current surface`)\n }\n if (requestedStartIdx > requestedEndIdx) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n let startIdx = requestedStartIdx\n let endIdx = requestedEndIdx\n // First pass: nudge inward to the nearest balanced cuts.\n while (startIdx <= endIdx && !toolPairingBalancedBefore(session, nodes[startIdx]!)) {\n startIdx += 1\n }\n while (endIdx >= startIdx && !toolPairingBalancedAfter(session, nodes[endIdx]!)) {\n endIdx -= 1\n }\n if (startIdx <= endIdx) {\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n // Second pass: the inward pass collapsed (a lone tool message) — expand\n // outward from the REQUESTED span to the smallest balanced enclosing pair.\n startIdx = requestedStartIdx\n endIdx = requestedEndIdx\n while (startIdx > 0 && !toolPairingBalancedBefore(session, nodes[startIdx]!)) {\n startIdx -= 1\n }\n while (endIdx < nodes.length - 1 && !toolPairingBalancedAfter(session, nodes[endIdx]!)) {\n endIdx += 1\n }\n if (toolPairingBalancedBefore(session, nodes[startIdx]!) && toolPairingBalancedAfter(session, nodes[endIdx]!)) {\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n}\n\n/** The surface seqs shadowed by the inclusive positional span. */\nexport function shadowedSeqsOf(session: Session, start: number, end: number): number[] {\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(start)\n const endIdx = nodes.indexOf(end)\n return nodes.slice(startIdx, endIdx + 1)\n}\n\nexport interface CompactionTransactionInput {\n readonly start: number\n readonly end: number\n readonly shadowedSeqs: readonly number[]\n readonly summary: ContentBlock[]\n readonly shadowedTokenCount: number\n readonly provider: string\n readonly model: string\n}\n\n/**\n * Run one durable compression transaction. Throws on invalid state; on success\n * the four events are in the log and the surface has one summary node.\n */\nexport function runCompactionTransaction(\n session: Session,\n input: CompactionTransactionInput,\n): { compactionId: string; seqs: number[] } {\n assertNoActiveCompaction(session.events)\n const turn = findOpenTurn(session.events)\n const compactionId = CompactionId(randomUUID())\n const seqs: number[] = []\n\n seqs.push(session.append('compaction/start', { compactionId, turn }).seq)\n seqs.push(session.append('compaction/summary', {\n compactionId,\n summary: input.summary,\n shadowedRange: { start: input.start, end: input.end },\n shadowedSeqs: [...input.shadowedSeqs],\n shadowedTokenCount: input.shadowedTokenCount,\n provider: input.provider,\n model: input.model,\n }).seq)\n\n const message = createUserMessage({\n content: input.summary,\n source: compactCheckpointSource(compactionId),\n })\n seqs.push(session.append('user/message', message, {\n surfaceOp: { op: 'replace', start: input.start, end: input.end },\n sourceEventSeqs: [...input.shadowedSeqs],\n }).seq)\n\n seqs.push(session.append('compaction/end', { compactionId, turn }).seq)\n return { compactionId, seqs }\n}\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const ledger: AcpBlockLedgerEntry[] = []\n for (const event of events) {\n if (event.type !== 'compaction/summary') continue\n const data = event.data\n // Blocks written before the token-accounting fix carry shadowedTokenCount\n // 0; backfill from the shadowed originals still in the log so acp_status\n // reports real reclaimed tokens.\n let shadowedTokenCount = data.shadowedTokenCount\n if (shadowedTokenCount === 0) {\n shadowedTokenCount = 0\n for (const seq of data.shadowedSeqs) {\n const original = events[seq]\n if (original !== undefined) shadowedTokenCount += defaultCountTokens(extractEventText(original))\n }\n }\n ledger.push({\n blockId: data.compactionId,\n summary: extractText(data.summary),\n shadowedSeqs: [...data.shadowedSeqs],\n shadowedTokenCount,\n start: data.shadowedRange.start,\n end: data.shadowedRange.end,\n })\n }\n return ledger\n}\n\n/** One self-computed compressible span of the current surface. */\nexport interface SeqCompressibleRange {\n readonly start: number\n readonly end: number\n readonly count: number\n readonly tokens: number\n}\n\n/** Whether a surface user message is a compaction checkpoint node (already compressed). */\nfunction isCheckpointNode(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\n/**\n * Compute compressible spans directly from the surface — independent of the\n * kernel's ref map, which can drift after surface replacements in long\n * sessions and hide large tool results from the nudge range table. Skips the\n * recent protected tail, the last user message, and compaction checkpoints;\n * edges are then balanced through resolveSurfaceRange. Ranges are ordered by\n * size (largest reclaimed first).\n */\nexport function buildCompressibleSeqRanges(\n session: Session,\n opts: { preserveRecent?: number } = {},\n): SeqCompressibleRange[] {\n const nodes = session.surface.nodes\n const preserve = opts.preserveRecent ?? 5\n const protectedSeqs = new Set<number>()\n for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq)\n for (let index = nodes.length - 1; index >= 0; index -= 1) {\n const event = session.events[nodes[index]!]\n if (event?.type === 'user/message' && !isCheckpointNode(event)) {\n protectedSeqs.add(nodes[index]!)\n break\n }\n }\n const raw: SeqCompressibleRange[] = []\n let cur: SeqCompressibleRange | null = null\n const flush = (): void => {\n if (cur !== null) raw.push(cur)\n cur = null\n }\n for (const seq of nodes) {\n const event = session.events[seq]\n if (event === undefined || protectedSeqs.has(seq) || isCheckpointNode(event)) {\n flush()\n continue\n }\n const tokens = defaultCountTokens(extractEventText(event))\n if (cur === null) {\n cur = { start: seq, end: seq, count: 1, tokens }\n } else {\n cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens }\n }\n }\n flush()\n const out: SeqCompressibleRange[] = []\n for (const range of raw) {\n try {\n const { start, end } = resolveSurfaceRange(session, range.start, range.end)\n const count = range.count\n out.push({ start, end, count, tokens: range.tokens })\n } catch {\n // Cannot be balanced into a compressible span — skip.\n }\n }\n return out.sort((a, b) => b.tokens - a.tokens)\n}\n","/**\n * M1 — session-log projection: DSH surface events → acp-kernel CoreMessage.\n *\n * The ACP kernel is message-array based; DSH is event-log based. This module\n * is the bridge in the direction the engine needs (projectEvent /\n * eventsToCoreMessages). The reverse direction (CoreMessage[] → session\n * appends) is the M5 region transaction's job.\n * Mirrors billion-context-pi's `projectMessage`/`entriesToCoreMessages`\n * against DSH event shapes (see V-verification: SurfaceEventType =\n * 'user/message' | 'assistant/message' | 'tool/result').\n * @module billion-context-pi-dsh/messages\n */\n\nimport type { CoreMessage } from 'acp-kernel'\nimport type { SessionEvent } from '@deepseek-ai/dsh-session'\n\n/**\n * Extract plain text from a DSH content block array or string.\n *\n * Recursive: a real DSH `tool-result` block is `{ type: 'tool-result',\n * toolCallId, content: ContentBlock[] }` — the inner `content` array holds\n * the actual `text` blocks, so a top-level-only walk would drop every tool\n * result from the projection (and with it the seq's ref assignment, breaking\n * compress boundary resolution). Nested arrays are flattened depth-first.\n */\nexport function extractText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown; content?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text)\n } else if (Array.isArray(b.content)) {\n parts.push(extractText(b.content))\n }\n }\n return parts.join('\\n')\n}\n\ninterface ToolCallBlock {\n type: 'tool-call'\n id?: string\n name?: string\n arguments?: unknown\n}\n\nfunction toolCallsOf(content: unknown): ToolCallBlock[] {\n if (!Array.isArray(content)) return []\n return content.filter((b): b is ToolCallBlock => (b as { type?: string }).type === 'tool-call')\n}\n\nfunction stringifyArgs(args: unknown): string {\n if (!args) return ''\n if (typeof args === 'string') return args\n try {\n return JSON.stringify(args)\n } catch {\n return String(args)\n }\n}\n\n/**\n * Project one surface message event into CoreMessage(s).\n * - user/message → user text (verbatim content)\n * - assistant/message → assistant text, or one CoreMessage per tool-call\n * - tool/result → tool result (toolName/toolCallId, role 'tool')\n * Non-surface events project to nothing.\n */\nexport function projectEvent(event: SessionEvent): CoreMessage[] {\n switch (event.type) {\n case 'user/message': {\n const text = extractText((event.data as { content?: unknown }).content)\n return text.length > 0 ? [{ id: String(event.seq), role: 'user', contentType: 'text', text }] : []\n }\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = toolCallsOf(content)\n const text = extractText(content)\n if (calls.length === 0) {\n return text.trim().length > 0\n ? [{ id: String(event.seq), role: 'assistant', contentType: 'text', text }]\n : []\n }\n if (calls.length === 1) {\n const call = calls[0]!\n const argStr = stringifyArgs(call.arguments)\n const body = argStr && text ? `${text}\\n${argStr}` : argStr || text\n return [{\n id: String(event.seq),\n role: 'assistant',\n contentType: 'tool-call',\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: body,\n }]\n }\n return calls.map((call) => ({\n id: `${event.seq}#${call.id ?? ''}`,\n role: 'assistant' as const,\n contentType: 'tool-call' as const,\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: stringifyArgs(call.arguments) || text,\n }))\n }\n case 'tool/result': {\n const message = (event.data as {\n message?: { content?: unknown; toolName?: string; toolCallId?: string }\n }).message\n const text = extractText(message?.content)\n if (text.length === 0) return []\n return [{\n id: String(event.seq),\n role: 'tool',\n contentType: 'tool-result',\n toolName: message?.toolName ?? '',\n toolCallId: message?.toolCallId ?? '',\n text,\n }]\n }\n default:\n return []\n }\n}\n\n/** Project a session's message events into CoreMessage[] in log order. */\nexport function eventsToCoreMessages(events: readonly SessionEvent[]): CoreMessage[] {\n const out: CoreMessage[] = []\n for (const event of events) out.push(...projectEvent(event))\n return out\n}\n\n/** The surface-visible message events of a session, in model-visible order. */\nexport function surfaceEventsOf(session: import('@deepseek-ai/dsh-session').Session): SessionEvent[] {\n return session.surface.nodes\n .map((seq) => session.events[seq])\n .filter((event): event is SessionEvent => event !== undefined)\n}\n\n/** Extract the model-facing text of any surface message event. */\nexport function extractEventText(event: SessionEvent): string {\n switch (event.type) {\n case 'user/message':\n return extractText((event.data as { content?: unknown }).content)\n case 'assistant/message':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n case 'tool/result':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n default:\n return ''\n }\n}\n","/**\n * M4 — the `/acp` slash command: a human-friendly window into the same\n * machinery the model tools expose (status, one-shot compress, decompress).\n * @module billion-context-dsh/commands\n */\n\nimport type { CommandDefinition } from '@deepseek-ai/dsh-commands'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { ToolEnvironment } from './tools.ts'\nimport {\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { defaultConfig, defaultCountTokens } from 'acp-kernel'\n\nfunction statusText(env: ToolEnvironment, agent: Agent): string {\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n const limit = env.modelContextLimit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ]\n for (const block of ledger.slice(0, 10)) {\n lines.push(` - ${block.blockId.slice(0, 8)}: seqs ${block.start}..${block.end} — ${block.summary.slice(0, 80)}`)\n }\n return lines.join('\\n')\n}\n\nfunction compressText(env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 3) {\n return '/acp compress <startSeq> <endSeq> <summary...>'\n }\n const startSeq = Number(args[0])\n const endSeq = Number(args[1])\n const summary = args.slice(2).join(' ')\n if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {\n return '/acp compress: startSeq and endSeq must be integers'\n }\n const session = agent.session\n const { start, end } = resolveSurfaceRange(session, startSeq, endSeq)\n const shadowed = shadowedSeqsOf(session, startSeq, endSeq)\n let shadowedTokens = 0\n for (const seq of shadowed) {\n const event = session.events[seq]\n if (event !== undefined) shadowedTokens += defaultCountTokens(extractEventText(event))\n }\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`\n}\n\nfunction decompressText(_env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 1) return '/acp decompress <blockId>'\n const session = agent.session\n const ledger = rebuildBlockLedger(session.events)\n const block = ledger.find((entry) => entry.blockId.startsWith(args[0]!))\n if (block === undefined) return `block \"${args[0]}\" not found (see /acp status)`\n const parts = block.shadowedSeqs\n .map((seq) => extractEventText(session.events[seq]!))\n .filter((text) => text.length > 0)\n return `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`\n}\n\n/** Register the /acp command (idempotent per engine). */\nexport function acpCommand(env: ToolEnvironment): CommandDefinition {\n return {\n name: 'acp',\n description:\n 'Active Context Pruning — model-driven context compression. '\n + 'Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId>',\n handler: async (invocation) => {\n const raw = invocation.rawInput.trim()\n if (raw === '' || raw === 'status') {\n return { kind: 'success', text: statusText(env, invocation.agent) }\n }\n if (raw.startsWith('compress')) {\n return { kind: 'success', text: compressText(env, invocation.agent, raw.slice('compress'.length).trim().split(/\\s+/) ) }\n }\n if (raw.startsWith('decompress')) {\n return { kind: 'success', text: decompressText(env, invocation.agent, raw.slice('decompress'.length).trim().split(/\\s+/)) }\n }\n return { kind: 'error', text: `unknown /acp subcommand \"${raw.split(/\\s+/)[0]}\" — use status | compress | decompress` }\n },\n }\n}\n","/**\n * M4 — ACP nudge: the kernel's compression recommendation, rendered as an\n * injected user message with a seq-based compressible-range table (D1:\n * \"seq is the ref\" — DSH has no in-memory message rewrite hook, so the model\n * targets ranges by surface seq rather than by <acp> tags).\n * @module billion-context-dsh/nudge\n */\n\nimport {\n defaultCountTokens,\n type CompressionCore,\n type CoreMessage,\n type NudgeDecision,\n} from 'acp-kernel'\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { eventsToCoreMessages, surfaceEventsOf } from './messages.ts'\nimport { buildCompressibleSeqRanges, findOpenTurn } from './region.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\n\n/** Kernel inputs the nudge path shares with the compress tool. */\nexport interface NudgeEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n}\n\nexport interface NudgeOutcome {\n readonly message: UserMessage\n readonly emergency: boolean\n}\n\n/**\n * Render the compressible-range table as seq refs for the model.\n * Computed directly from the surface (not the kernel's ref map, which can\n * drift and hide large tool results) — see buildCompressibleSeqRanges.\n */\nexport function rangeTable(session: import('@deepseek-ai/dsh-session').Session): string {\n const ranges = buildCompressibleSeqRanges(session).slice(0, 6)\n if (ranges.length === 0) return ''\n const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} — ${range.count} messages, ~${range.tokens} tokens`)\n return [\n '',\n 'Compressible ranges (refs are surface seqs):',\n ...lines,\n 'Compress with: compress({ content: [{ startSeq, endSeq, summary }] })',\n ].join('\\n')\n}\n\n/**\n * The token count driving pressure decisions. Prefer the host token meter's\n * SURFACE tokens (the input-side heuristic total — matches acp_status and the\n * UI's message-token view). `totalTokens` is request-AND-RESPONSE pressure and\n * can far exceed the window in long sessions (observed 230% against a real\n * ~20% occupancy), so it is not used. Falls back to the fast heuristic when\n * the meter is absent (tests, minimal hosts).\n */\nfunction measuredTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n const meter = agent.ctx?.get?.('tokenMeter') as\n | { measure?: (session: unknown) => { surfaceTokens?: number } }\n | undefined\n const surface = meter?.measure?.(agent.session)?.surfaceTokens\n if (typeof surface === 'number' && surface > 0) return surface\n return coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n}\n\n/**\n * Decide and build one nudge message for the agent's next pre-step. Returns\n * null when the kernel recommends no nudge or one was already injected for the\n * current turn (emergency nudges always bypass the dedup). Also advances the\n * in-memory kernel state (ref assignment) so the compress tool can resolve\n * seq → mNNNNN refs.\n */\nexport function buildNudge(\n agent: Agent,\n env: NudgeEnvironment,\n lastNudgeTurn: Map<string, number>,\n): NudgeOutcome | null {\n const session = agent.session\n const state = env.store.stateFor(session)\n const coreMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = measuredTokenCount(agent, coreMessages)\n const config = kernelConfigFor(env)\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n\n const nudge = turn.nudge\n if (nudge === undefined || !nudge.shouldInject) return null\n const emergency = nudge.breakdown?.emergencyOverride === 1\n\n const turnNumber = findOpenTurn(session.events) ?? 0\n const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber\n if (alreadyShown) return null\n lastNudgeTurn.set(session.id, turnNumber)\n\n const text = buildNudgeText(nudge, emergency, session)\n const message = createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'acp-nudge' },\n })\n return { message, emergency }\n}\n\n/**\n * A short ADVISORY nudge — ACP is model-driven, the model decides whether and\n * when to compress. Full guidance (tools, philosophy, summary rules) lives in\n * the system prompt once, not in every nudge.\n */\nfunction buildNudgeText(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n): string {\n // Cap the reported percentage at 100: a broken measurement (e.g. response\n // pressure folded in) must never surface as an absurd \"230%\" to the model.\n const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100)\n const frame = emergency\n ? `⚠️ Context usage is at ${pct}% of the window — nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.`\n : `Context usage is at ${pct}%. This is a suggestion, not a requirement — you decide whether and when to compress.`\n const guidance = 'Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.'\n return [frame, '', guidance, rangeTable(session)].join('\\n')\n}\n","/**\n * M4 — the ACP system-prompt section (DSH counterpart of billion-context-pi's\n * ACP_SYSTEM_PROMPT): the load-bearing compression guidance lives here, ONCE,\n * instead of being re-sent with every nudge. The nudge itself stays a short,\n * advisory notice — ACP is model-driven, the model decides whether and when\n * to compress (never \"compress now\").\n * @module billion-context-dsh/system-prompt\n */\n\nimport { COMPRESS_PHILOSOPHY } from 'acp-kernel'\n\nexport const ACP_SYSTEM_PROMPT = `Active Context Pruning — model-driven context management\n\nYOU decide whether and when to compress context. Nothing forces you: the injected \"nudge\" is a suggestion, not an order, and you may ignore it when compression would not help. Compress only ranges you have genuinely consumed (read tool outputs, finished explorations, superseded steps) that the current work no longer needs verbatim.\n\n${COMPRESS_PHILOSOPHY}\n\nCompression tools (refs are SURFACE SEQS, not ids):\n- compress: replace a seq range with your dense self-contained summary. compress({ content: [{ startSeq, endSeq, summary }] }). Edges are auto-balanced to tool-call/result boundaries; a trailing #callId fragment in a seq is ignored. Ranges must be on the current surface — stale seqs fail with guidance.\n- decompress: recover a compressed block's original content, read-only. decompress({ blockId }).\n- search_context: find information inside compressed blocks BEFORE decompressing. search_context({ query }).\n- acp_status: current context usage and the live compressible-range list. Run it before compressing when in doubt.\n\nWhen you write a summary, it becomes the ONLY record of that range: keep file paths, signatures, exact values, decisions, and error strings verbatim so a later reader (or you, after decompress) can continue without the original. Never reuse historical seqs — the surface moves as messages land and compress; verify with acp_status.`\n\n/** System-prompt section order: tool guidance lives in 100–199. */\nexport const ACP_SYSTEM_PROMPT_ORDER = 150\n"],"mappings":"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","names":["defaultCountTokens","defaultCountTokens","defaultCountTokens","defaultCountTokens","createUserMessage","defaultCountTokens","createUserMessage"]}
|
package/dist/nudge.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface NudgeOutcome {
|
|
|
19
19
|
readonly message: UserMessage;
|
|
20
20
|
readonly emergency: boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Render the compressible-range table as seq refs for the model.
|
|
24
|
+
* Computed directly from the surface (not the kernel's ref map, which can
|
|
25
|
+
* drift and hide large tool results) — see buildCompressibleSeqRanges.
|
|
26
|
+
*/
|
|
27
|
+
export declare function rangeTable(session: import('@deepseek-ai/dsh-session').Session): string;
|
|
22
28
|
/**
|
|
23
29
|
* Decide and build one nudge message for the agent's next pre-step. Returns
|
|
24
30
|
* null when the kernel recommends no nudge or one was already injected for the
|
package/dist/region.d.ts
CHANGED
|
@@ -27,12 +27,14 @@ export declare function findOpenTurn(events: readonly SessionEvent[]): number |
|
|
|
27
27
|
/** Reject a second concurrent compaction for the same session. */
|
|
28
28
|
export declare function assertNoActiveCompaction(events: readonly SessionEvent[]): void;
|
|
29
29
|
/**
|
|
30
|
-
* Validate one inclusive surface span and adjust its edges to
|
|
31
|
-
* tool-pairing-balanced
|
|
32
|
-
* edge that sits inside a tool-call/result pair is nudged
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* Validate one inclusive surface span and adjust its edges to a
|
|
31
|
+
* tool-pairing-balanced range. Missing or reversed ranges still throw. An
|
|
32
|
+
* edge that sits inside a tool-call/result pair is first nudged inward to the
|
|
33
|
+
* nearest clean cut; if that collapses the range (e.g. the model asked for a
|
|
34
|
+
* SINGLE tool result, which can never be balanced alone), the range EXPANDS
|
|
35
|
+
* outward to the enclosing balanced pair instead — a lone tool message is
|
|
36
|
+
* almost always a "consumed output" the model genuinely wants to compress.
|
|
37
|
+
* The returned range is what a caller should actually shadow.
|
|
36
38
|
*/
|
|
37
39
|
export declare function resolveSurfaceRange(session: Session, start: number, end: number): {
|
|
38
40
|
start: number;
|
|
@@ -59,3 +61,21 @@ export declare function runCompactionTransaction(session: Session, input: Compac
|
|
|
59
61
|
};
|
|
60
62
|
/** Rebuild the block ledger from the durable log (no kernel state needed). */
|
|
61
63
|
export declare function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[];
|
|
64
|
+
/** One self-computed compressible span of the current surface. */
|
|
65
|
+
export interface SeqCompressibleRange {
|
|
66
|
+
readonly start: number;
|
|
67
|
+
readonly end: number;
|
|
68
|
+
readonly count: number;
|
|
69
|
+
readonly tokens: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Compute compressible spans directly from the surface — independent of the
|
|
73
|
+
* kernel's ref map, which can drift after surface replacements in long
|
|
74
|
+
* sessions and hide large tool results from the nudge range table. Skips the
|
|
75
|
+
* recent protected tail, the last user message, and compaction checkpoints;
|
|
76
|
+
* edges are then balanced through resolveSurfaceRange. Ranges are ordered by
|
|
77
|
+
* size (largest reclaimed first).
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildCompressibleSeqRanges(session: Session, opts?: {
|
|
80
|
+
preserveRecent?: number;
|
|
81
|
+
}): SeqCompressibleRange[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "billion-context-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Active Context Pruning (ACP) for the DeepSeek Harness — model-driven context management as a CompactionEngine backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@deepseek-ai/dsh-compaction": "0.1.0-rc.6",
|
|
42
42
|
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
43
43
|
"@deepseek-ai/dsh-session": "0.1.0-rc.6",
|
|
44
|
+
"@deepseek-ai/dsh-token-meter": "^0.1.0-rc.6",
|
|
44
45
|
"@deepseek-ai/dsh-tools": "0.1.0-rc.6",
|
|
45
46
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
46
47
|
"@types/node": "^26.1.2",
|