dsh-context-compression-improved 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/workflows/ci.yml +39 -0
  3. package/CHANGELOG.ja.md +39 -0
  4. package/CHANGELOG.ko.md +39 -0
  5. package/CHANGELOG.md +135 -0
  6. package/CHANGELOG.zh.md +39 -0
  7. package/CONTRIBUTING.md +22 -0
  8. package/README.ja.md +104 -0
  9. package/README.ko.md +103 -0
  10. package/README.md +89 -12
  11. package/README.zh.md +87 -12
  12. package/SECURITY.md +18 -0
  13. package/THIRD_PARTY_NOTICES.md +7 -31
  14. package/docs/installation.ja.md +76 -0
  15. package/docs/installation.ko.md +76 -0
  16. package/docs/installation.md +76 -0
  17. package/docs/installation.zh.md +76 -0
  18. package/docs/repair-log.md +582 -0
  19. package/eslint.config.js +30 -0
  20. package/package.json +84 -81
  21. package/packages/selector/LICENSE +21 -0
  22. package/packages/selector/README.md +26 -0
  23. package/packages/selector/README.zh.md +26 -0
  24. package/packages/selector/THIRD_PARTY_NOTICES.md +38 -0
  25. package/packages/selector/docs/history-tool-call-working-set-spec.md +112 -0
  26. package/packages/selector/docs/native-tool-result-selector-spec.md +34 -0
  27. package/packages/selector/docs/subagent-cache-reuse-spec.md +46 -0
  28. package/packages/selector/lib/style.css +308 -0
  29. package/packages/selector/package.json +115 -0
  30. package/{screenshots.json → packages/selector/screenshots.json} +6 -6
  31. package/packages/selector/src/client/CompressionProfileControls.tsx +229 -0
  32. package/packages/selector/src/client/CompressionProfileSelector.module.css +170 -0
  33. package/packages/selector/src/client/CompressionProfileSelector.tsx +79 -0
  34. package/packages/selector/src/client/CustomPolicyEditor.tsx +216 -0
  35. package/packages/selector/src/client/EstimatorControls.tsx +281 -0
  36. package/packages/selector/src/client/decode.ts +49 -0
  37. package/packages/selector/src/client/index.ts +111 -0
  38. package/packages/selector/src/client/locales.ts +198 -0
  39. package/packages/selector/src/client/preset-options.ts +70 -0
  40. package/packages/selector/src/client/settings-section.tsx +126 -0
  41. package/packages/selector/src/css-modules.d.ts +6 -0
  42. package/packages/selector/src/deepseek-v4-tokenizer.ts +210 -0
  43. package/packages/selector/src/estimator-catalog.ts +104 -0
  44. package/packages/selector/src/index.ts +327 -0
  45. package/packages/selector/src/invariant.ts +113 -0
  46. package/packages/selector/src/preset-overlay.ts +567 -0
  47. package/packages/selector/src/profiles.ts +342 -0
  48. package/packages/selector/src/pruner/content.ts +188 -0
  49. package/packages/selector/src/pruner/session.ts +94 -0
  50. package/packages/selector/src/pruner/state.ts +43 -0
  51. package/packages/selector/src/pruner/tuning.ts +23 -0
  52. package/packages/selector/src/pruner/types.ts +60 -0
  53. package/packages/selector/src/pruner.ts +2144 -0
  54. package/packages/selector/src/runtime/adaptive-cost.ts +194 -0
  55. package/packages/selector/src/runtime/audit.ts +215 -0
  56. package/packages/selector/src/runtime/config.ts +613 -0
  57. package/packages/selector/src/runtime/custom-policy.ts +278 -0
  58. package/packages/selector/src/runtime/deepseek-official-pricing.ts +298 -0
  59. package/packages/selector/src/runtime/deepseek-v4-vision-tokens.ts +254 -0
  60. package/packages/selector/src/runtime/measurement.ts +403 -0
  61. package/packages/selector/src/runtime/reducers.ts +656 -0
  62. package/packages/selector/src/runtime/retrieve.ts +457 -0
  63. package/packages/selector/src/runtime/session-events.ts +17 -0
  64. package/packages/selector/src/runtime/tail-trim.ts +166 -0
  65. package/packages/selector/src/runtime/token-count.ts +72 -0
  66. package/packages/selector/src/runtime/tokenpilot/dedup.ts +81 -0
  67. package/packages/selector/src/runtime/tokenpilot/estimator.ts +183 -0
  68. package/packages/selector/src/runtime/tokenpilot/locator.ts +128 -0
  69. package/packages/selector/src/runtime/tokenpilot/read-state.ts +77 -0
  70. package/packages/selector/src/runtime/types.ts +309 -0
  71. package/packages/selector/src/runtime/value.ts +48 -0
  72. package/packages/selector/tests/auto-compact.client.spec.tsx +226 -0
  73. package/packages/selector/tests/built/client-artifact.spec.ts +51 -0
  74. package/packages/selector/tests/cache-prefix-audit.spec.ts +123 -0
  75. package/packages/selector/tests/code-skeleton.client.spec.ts +88 -0
  76. package/packages/selector/tests/custom-contract.client.spec.ts +202 -0
  77. package/packages/selector/tests/estimator-catalog.spec.ts +70 -0
  78. package/packages/selector/tests/estimator-channel.client.spec.tsx +247 -0
  79. package/packages/selector/tests/estimator-route-registration.host.spec.ts +176 -0
  80. package/packages/selector/tests/host-preset-overlay.host.spec.ts +204 -0
  81. package/packages/selector/tests/preset-options-write.client.spec.ts +181 -0
  82. package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +196 -0
  83. package/packages/selector/tests/preset-overlay.host.spec.ts +243 -0
  84. package/packages/selector/tests/profiles.client.spec.tsx +434 -0
  85. package/packages/selector/tests/public/package-contract.client.spec.ts +33 -0
  86. package/packages/selector/tests/runtime/adaptive-cost.spec.ts +167 -0
  87. package/packages/selector/tests/runtime/audit.spec.ts +129 -0
  88. package/packages/selector/tests/runtime/auto-compact-config.spec.ts +523 -0
  89. package/packages/selector/tests/runtime/code-skeleton.spec.ts +141 -0
  90. package/packages/selector/tests/runtime/deepseek-official-pricing.spec.ts +186 -0
  91. package/packages/selector/tests/runtime/deepseek-v4-tokenizer.spec.ts +122 -0
  92. package/packages/selector/tests/runtime/deepseek-v4-vision-tokens.spec.ts +122 -0
  93. package/packages/selector/tests/runtime/fixtures/profile-baseline.json +273 -0
  94. package/packages/selector/tests/runtime/fixtures/tokenizer-golden.json +106 -0
  95. package/packages/selector/tests/runtime/fixtures/vision-golden.json +459 -0
  96. package/packages/selector/tests/runtime/public/public-runtime.spec.ts +2531 -0
  97. package/packages/selector/tests/runtime/session-events.spec.ts +27 -0
  98. package/packages/selector/tests/runtime/tokenizer-golden.spec.ts +53 -0
  99. package/packages/selector/tests/runtime/tokenpilot/dedup.spec.ts +52 -0
  100. package/packages/selector/tests/runtime/tokenpilot/estimator.spec.ts +56 -0
  101. package/packages/selector/tests/runtime/tokenpilot/locator.spec.ts +76 -0
  102. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +100 -0
  103. package/packages/selector/tests/runtime/tokenpilot/read-state.spec.ts +58 -0
  104. package/packages/selector/tests/runtime/value.spec.ts +23 -0
  105. package/packages/selector/tests/standing-generation.host.spec.ts +631 -0
  106. package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +250 -0
  107. package/packages/selector/tests/support/cache-prefix-audit.ts +105 -0
  108. package/packages/selector/tests/support/mock-adapter.ts +37 -0
  109. package/packages/selector/tests/support/ui-primitives.tsx +34 -0
  110. package/packages/selector/tsconfig.json +11 -0
  111. package/packages/selector/tsdown.client.config.ts +102 -0
  112. package/packages/selector/tsdown.config.ts +20 -0
  113. package/pnpm-workspace.yaml +19 -0
  114. package/scripts/capture-profile-baseline.ts +80 -0
  115. package/scripts/generate-tokenizer-fixtures.py +81 -0
  116. package/scripts/generate-vision-fixtures.py +208 -0
  117. package/scripts/packed-components-smoke.ts +713 -0
  118. package/scripts/packed-install-e2e.ts +1072 -0
  119. package/scripts/verify-release.ts +300 -0
  120. package/tests/TEST_INVENTORY.md +42 -0
  121. package/tsconfig.base.json +18 -0
  122. package/tsconfig.json +7 -0
  123. package/tsconfig.scripts.json +13 -0
  124. package/tsconfig.tests.json +15 -0
  125. package/vitest.built.config.ts +9 -0
  126. package/vitest.config.ts +43 -0
  127. /package/{assets → packages/selector/assets}/deepseek-v4/LICENSE.DeepSeek-V4-Pro.txt +0 -0
  128. /package/{assets → packages/selector/assets}/deepseek-v4/manifest.json +0 -0
  129. /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer.json +0 -0
  130. /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer_config.json +0 -0
  131. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/LICENSE.DeepSeek-V4-Flash-Vision-Exp.txt +0 -0
  132. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/manifest.json +0 -0
  133. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer.json +0 -0
  134. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer_config.json +0 -0
  135. /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-profiles.jpg +0 -0
  136. /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-settings.png +0 -0
  137. /package/{cordis.patch.yml → packages/selector/cordis.patch.yml} +0 -0
  138. /package/{dsh.plugin.json → packages/selector/dsh.plugin.json} +0 -0
  139. /package/{lib → packages/selector/lib}/client.d.ts +0 -0
  140. /package/{lib → packages/selector/lib}/client.js +0 -0
  141. /package/{lib → packages/selector/lib}/config.js +0 -0
  142. /package/{lib → packages/selector/lib}/index.d.ts +0 -0
  143. /package/{lib → packages/selector/lib}/index.js +0 -0
  144. /package/{lib → packages/selector/lib}/invariant.d.ts +0 -0
  145. /package/{lib → packages/selector/lib}/invariant.js +0 -0
  146. /package/{lib → packages/selector/lib}/pruner.d.ts +0 -0
  147. /package/{lib → packages/selector/lib}/pruner.js +0 -0
  148. /package/{lib → packages/selector/lib}/tail-trim.js +0 -0
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+ """Regenerate the tokenizer golden fixture for the Node loader equivalence tests.
3
+
4
+ Loads each pinned ``tokenizer.json`` shipped in ``packages/selector/assets`` with
5
+ the official Hugging Face ``tokenizers`` Python library (the same Rust core the
6
+ ``transformers`` tokenizer for these repositories uses) and records the token
7
+ counts the Node runtime must reproduce exactly.
8
+
9
+ Usage:
10
+ python3 scripts/generate-tokenizer-fixtures.py
11
+
12
+ Requires: pip install tokenizers
13
+ Output: packages/selector/tests/runtime/fixtures/tokenizer-golden.json
14
+ """
15
+
16
+ import json
17
+ import pathlib
18
+ import platform
19
+
20
+ REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
21
+ ASSETS = {
22
+ "deepseek-ai/DeepSeek-V4-Pro": REPO_ROOT / "packages/selector/assets/deepseek-v4",
23
+ "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp": REPO_ROOT
24
+ / "packages/selector/assets/deepseek-v4-vision-exp",
25
+ }
26
+ OUTPUT = REPO_ROOT / "packages/selector/tests/runtime/fixtures/tokenizer-golden.json"
27
+
28
+ CASES = [
29
+ ("english prose", "The quick brown fox jumps over the lazy dog."),
30
+ ("simplified chinese", "上下文压缩选择器在长任务中回收工具结果 token。"),
31
+ ("mixed zh-en", "DeepSeek Harness 的 tool result 压缩,Fresh/Aggregate/History 三个阶段。"),
32
+ ("json tool output", '{"status":"ok","items":[{"id":1,"path":"/tmp/a.txt"}],"count":1}'),
33
+ ("python code", "def solve(width: int, height: int) -> int:\n return width * height // 2\n"),
34
+ ("shell transcript", "$ pnpm run test\n> vitest run\n\n Test Files 12 passed (12)\n"),
35
+ ("unicode whitespace", "line1\nline2\r\nline3\tline4 double spaces"),
36
+ ("deepseek chat special tokens", "<|begin▁of▁sentence|><|User|>hello<|Assistant|><think>\n"),
37
+ ("vision image placeholder", "before<|deepseek_image|>after"),
38
+ ("empty string", ""),
39
+ ("single ascii char", "a"),
40
+ ("emoji", "compression 🧠✅ done"),
41
+ ]
42
+
43
+ MANIFEST_REVISIONS = {}
44
+ for repository, asset_dir in ASSETS.items():
45
+ manifest = json.loads((asset_dir / "manifest.json").read_text(encoding="utf-8"))
46
+ MANIFEST_REVISIONS[repository] = manifest["revision"]
47
+
48
+
49
+ def main() -> None:
50
+ from tokenizers import Tokenizer
51
+
52
+ tokenizers = {}
53
+ for repository, asset_dir in ASSETS.items():
54
+ # Python `tokenizers` loads the fast-tokenizer definition directly from
55
+ # tokenizer.json; tokenizer_config.json only governs transformers-level
56
+ # special-token insertion, which these counts deliberately disable.
57
+ tokenizers[repository] = Tokenizer.from_file(str(asset_dir / "tokenizer.json"))
58
+
59
+ cases = []
60
+ for label, text in CASES:
61
+ counts = {}
62
+ for repository, tokenizer in tokenizers.items():
63
+ encoding = tokenizer.encode(text, add_special_tokens=False)
64
+ counts[repository] = len(encoding.ids)
65
+ cases.append({"label": label, "text": text, "counts": counts})
66
+
67
+ import tokenizers
68
+
69
+ fixture = {
70
+ "generator": f"tokenizers {tokenizers.__version__} on {platform.system()}",
71
+ "revision": "1",
72
+ "tokenizers": {repository: MANIFEST_REVISIONS[repository] for repository in sorted(ASSETS)},
73
+ "cases": cases,
74
+ }
75
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
76
+ OUTPUT.write_text(json.dumps(fixture, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
77
+ print(f"wrote {OUTPUT} with {len(cases)} cases")
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main()
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env python3
2
+ """Regenerate the DeepSeek V4 Flash Vision image-token golden fixture.
3
+
4
+ Downloads the official ``inference/image_processor.py`` and ``config.json`` from
5
+ the pinned immutable revision of ``deepseek-ai/DeepSeek-V4-Flash-Vision-Exp``,
6
+ runs the official pipeline over synthetic images of many sizes and aspect
7
+ ratios, and records the token counts the Node runtime must reproduce exactly.
8
+
9
+ Every number in the fixture is produced by the official implementation; this
10
+ script never re-implements the arithmetic.
11
+
12
+ Usage:
13
+ python3 scripts/generate-vision-fixtures.py [--workdir DIR]
14
+
15
+ Requires: pip install torch pillow
16
+ Output: packages/selector/tests/runtime/fixtures/vision-golden.json
17
+ """
18
+
19
+ import argparse
20
+ import io
21
+ import json
22
+ import math
23
+ import pathlib
24
+ import sys
25
+ import types
26
+ import urllib.request
27
+
28
+ REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
29
+ OUTPUT = REPO_ROOT / "packages/selector/tests/runtime/fixtures/vision-golden.json"
30
+ REVISION = "6821d6ad3681a4b137b066b76094fa82ebd0a380"
31
+ BASE = f"https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Vision-Exp/resolve/{REVISION}"
32
+
33
+ # (width, height) cases: squares, portrait/landscape, min-pixel upscale,
34
+ # the 8:1 aspect clamp in both orientations, the 384-token cap, and sizes
35
+ # around the patch grid boundaries.
36
+ SINGLE_SIZES = [
37
+ (14, 14),
38
+ (28, 14),
39
+ (56, 56),
40
+ (100, 100),
41
+ (224, 224),
42
+ (240, 180),
43
+ (180, 240),
44
+ (384, 384),
45
+ (512, 384),
46
+ (640, 480),
47
+ (800, 600),
48
+ (1024, 768),
49
+ (768, 1024),
50
+ (1280, 960),
51
+ (1600, 1200),
52
+ (2048, 1536),
53
+ (2000, 2000),
54
+ (4096, 4096),
55
+ (8, 8),
56
+ (4, 400),
57
+ (400, 4),
58
+ (64, 512),
59
+ (1152, 128),
60
+ (1024, 119),
61
+ (4200, 100),
62
+ (100, 4200),
63
+ (147, 147),
64
+ (1414, 1414),
65
+ ]
66
+
67
+ # start positions exercising every compress-pad alignment and several
68
+ # multi-image accumulated offsets.
69
+ START_POSITIONS = [0, 1, 2, 3, 4, 5, 7, 8, 17, 64, 101, 383, 384, 766, 767]
70
+
71
+
72
+ def png_bytes(width: int, height: int) -> bytes:
73
+ from PIL import Image
74
+
75
+ image = Image.new("RGB", (width, height), (127, 127, 127))
76
+ buffer = io.BytesIO()
77
+ image.save(buffer, format="PNG")
78
+ return buffer.getvalue()
79
+
80
+
81
+ def official_module(workdir: pathlib.Path) -> types.ModuleType:
82
+ source_path = workdir / "image_processor.py"
83
+ urllib.request.urlretrieve(f"{BASE}/inference/image_processor.py", source_path)
84
+ config_path = workdir / "config.json"
85
+ urllib.request.urlretrieve(f"{BASE}/config.json", config_path)
86
+ config = json.loads(config_path.read_text(encoding="utf-8"))
87
+
88
+ sys.path.insert(0, str(workdir))
89
+ import image_processor # noqa: PLC0415 (official module, downloaded on demand)
90
+
91
+ args = types.SimpleNamespace(
92
+ vision_patch_size=config["vision_patch_size"],
93
+ vision_downsample_ratio=config["vision_downsample_ratio"],
94
+ vision_max_n_token=config["vision_max_n_token"],
95
+ vision_min_pixels=config["vision_min_pixels"],
96
+ vision_max_wh_ratio=config["vision_max_wh_ratio"],
97
+ vocab_size=0,
98
+ )
99
+ return image_processor, args
100
+
101
+
102
+ def main() -> None:
103
+ parser = argparse.ArgumentParser()
104
+ parser.add_argument("--workdir", default="/tmp/dsvision-fixture-work")
105
+ options = parser.parse_args()
106
+ workdir = pathlib.Path(options.workdir)
107
+ workdir.mkdir(parents=True, exist_ok=True)
108
+
109
+ official, args = official_module(workdir)
110
+
111
+ single = []
112
+ for width, height in SINGLE_SIZES:
113
+ patches, n_vit_h, n_vit_w, n_llm_h, n_llm_w = official.load_image(
114
+ {"data": png_bytes(width, height)}, args
115
+ )
116
+ assert tuple(patches.shape) == (n_vit_h * n_vit_w, 3, args.vision_patch_size, args.vision_patch_size)
117
+ single.append(
118
+ {
119
+ "width": width,
120
+ "height": height,
121
+ "startTokenPos": 0,
122
+ "nLlmH": n_llm_h,
123
+ "nLlmW": n_llm_w,
124
+ "tokensAtStart0": 0,
125
+ }
126
+ )
127
+
128
+ # Official token totals depend on the running position; use one canonical
129
+ # size and sweep start positions through build_image_block directly.
130
+ canonical_width, canonical_height = 640, 480
131
+ _, _, _, n_llm_h, n_llm_w = official.load_image(
132
+ {"data": png_bytes(canonical_width, canonical_height)}, args
133
+ )
134
+ positions = []
135
+ for start in START_POSITIONS:
136
+ types_tensor, _perm = official.build_image_block(n_llm_h, n_llm_w, start)
137
+ positions.append(
138
+ {
139
+ "width": canonical_width,
140
+ "height": canonical_height,
141
+ "startTokenPos": start,
142
+ "nLlmH": n_llm_h,
143
+ "nLlmW": n_llm_w,
144
+ "tokens": int(types_tensor.numel()),
145
+ }
146
+ )
147
+
148
+ # Multi-image sequences mirror prepare_vl_inputs: each image starts at the
149
+ # running position accumulated from every earlier image's expanded block.
150
+ sequences = []
151
+ for sizes in [
152
+ [(640, 480), (800, 600), (1024, 768)],
153
+ [(2048, 1536), (147, 147), (4096, 4096)],
154
+ [(64, 512), (1152, 128), (4200, 100), (100, 4200)],
155
+ ]:
156
+ position = 3 # a small text prefix keeps this off the trivial 0 case
157
+ entries = []
158
+ for width, height in sizes:
159
+ _, _, _, grid_h, grid_w = official.load_image({"data": png_bytes(width, height)}, args)
160
+ types_tensor, _perm = official.build_image_block(grid_h, grid_w, position)
161
+ tokens = int(types_tensor.numel())
162
+ entries.append(
163
+ {
164
+ "width": width,
165
+ "height": height,
166
+ "startTokenPos": position,
167
+ "nLlmH": grid_h,
168
+ "nLlmW": grid_w,
169
+ "tokens": tokens,
170
+ }
171
+ )
172
+ position += tokens
173
+ sequences.append({"entries": entries})
174
+
175
+ # Token totals for every single size, each measured at its own start
176
+ # position sweep? Keep single sizes at a representative position instead:
177
+ # recompute each at a fixed non-zero start for a second data point.
178
+ for entry, (width, height) in zip(single, SINGLE_SIZES):
179
+ types_tensor, _perm = official.build_image_block(entry["nLlmH"], entry["nLlmW"], 0)
180
+ entry["tokensAtStart0"] = int(types_tensor.numel())
181
+
182
+ fixture = {
183
+ "source": {
184
+ "repository": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp",
185
+ "revision": REVISION,
186
+ "files": ["inference/image_processor.py", "config.json"],
187
+ },
188
+ "parameters": {
189
+ "visionPatchSize": args.vision_patch_size,
190
+ "visionDownsampleRatio": args.vision_downsample_ratio,
191
+ "visionMaxNTokens": args.vision_max_n_token,
192
+ "visionMinPixels": args.vision_min_pixels,
193
+ "visionMaxWhRatio": args.vision_max_wh_ratio,
194
+ },
195
+ "singleImages": single,
196
+ "startPositions": positions,
197
+ "sequences": sequences,
198
+ }
199
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
200
+ OUTPUT.write_text(json.dumps(fixture, indent=2) + "\n", encoding="utf-8")
201
+ print(
202
+ f"wrote {OUTPUT}: {len(single)} sizes, {len(positions)} start positions, "
203
+ f"{len(sequences)} multi-image sequences"
204
+ )
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main()