dsh-context-compression-improved 0.1.1 → 0.2.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.
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 +85 -82
  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,308 @@
1
+ .WcpPCW_settingsSection {
2
+ max-width: 720px;
3
+ color: var(--dsw-alias-label-primary);
4
+ flex-direction: column;
5
+ gap: 12px;
6
+ display: flex;
7
+ }
8
+
9
+ .WcpPCW_settingsTitle {
10
+ color: var(--dsw-alias-label-primary);
11
+ margin: 0;
12
+ font-size: 16px;
13
+ font-weight: 500;
14
+ line-height: 24px;
15
+ }
16
+
17
+ .WcpPCW_settingsDescription {
18
+ color: var(--dsw-alias-label-tertiary);
19
+ margin: 0;
20
+ font-size: 14px;
21
+ line-height: 22px;
22
+ }
23
+
24
+ .WcpPCW_root {
25
+ width: 100%;
26
+ }
27
+
28
+ .WcpPCW_profileGrid {
29
+ grid-template-columns: repeat(2, minmax(0, 1fr));
30
+ gap: 10px;
31
+ display: grid;
32
+ }
33
+
34
+ .WcpPCW_profileCard {
35
+ border: 1px solid var(--dsw-alias-border-l2);
36
+ min-height: 112px;
37
+ color: var(--dsw-alias-label-primary);
38
+ cursor: pointer;
39
+ text-align: left;
40
+ background: none;
41
+ border-radius: 10px;
42
+ flex-direction: column;
43
+ gap: 8px;
44
+ padding: 14px;
45
+ display: flex;
46
+ }
47
+
48
+ .WcpPCW_profileCard:hover:not(:disabled) {
49
+ background: var(--dsw-alias-interactive-bg-hover);
50
+ }
51
+
52
+ .WcpPCW_profileCard[aria-pressed="true"] {
53
+ border-color: var(--dsw-alias-label-primary);
54
+ }
55
+
56
+ .WcpPCW_profileCard:active:not(:disabled) {
57
+ transform: scale(.99);
58
+ }
59
+
60
+ .WcpPCW_profileCard:disabled {
61
+ cursor: default;
62
+ opacity: .6;
63
+ }
64
+
65
+ .WcpPCW_profileCard:focus-visible {
66
+ outline-offset: 2px;
67
+ outline: 2px solid;
68
+ }
69
+
70
+ .WcpPCW_profileCardTop {
71
+ align-items: center;
72
+ gap: 8px;
73
+ display: flex;
74
+ }
75
+
76
+ .WcpPCW_profileCardTitle {
77
+ font-size: 14px;
78
+ font-weight: 500;
79
+ line-height: 20px;
80
+ }
81
+
82
+ .WcpPCW_profileCurrent {
83
+ background: var(--dsw-alias-label-primary);
84
+ color: var(--dsw-alias-bg-base);
85
+ border-radius: 999px;
86
+ padding: 1px 6px;
87
+ font-size: 10px;
88
+ line-height: 14px;
89
+ }
90
+
91
+ .WcpPCW_profileCardDetail {
92
+ color: var(--dsw-alias-label-secondary);
93
+ font-size: 12px;
94
+ line-height: 18px;
95
+ }
96
+
97
+ .WcpPCW_button {
98
+ border: 1px solid var(--dsw-alias-border-l2);
99
+ width: 100%;
100
+ min-height: 34px;
101
+ color: var(--dsw-alias-label-primary);
102
+ cursor: pointer;
103
+ text-align: left;
104
+ background: none;
105
+ border-radius: 10px;
106
+ align-items: center;
107
+ gap: 8px;
108
+ padding: 6px 8px;
109
+ display: flex;
110
+ }
111
+
112
+ .WcpPCW_button:hover {
113
+ background: var(--dsw-alias-interactive-bg-hover);
114
+ }
115
+
116
+ .WcpPCW_button:disabled {
117
+ cursor: default;
118
+ opacity: .6;
119
+ }
120
+
121
+ .WcpPCW_copy {
122
+ flex: 1;
123
+ min-width: 0;
124
+ }
125
+
126
+ .WcpPCW_label {
127
+ color: var(--dsw-alias-label-tertiary);
128
+ font-size: 11px;
129
+ line-height: 14px;
130
+ }
131
+
132
+ .WcpPCW_value {
133
+ text-overflow: ellipsis;
134
+ white-space: nowrap;
135
+ font-size: 13px;
136
+ line-height: 17px;
137
+ overflow: hidden;
138
+ }
139
+
140
+ .WcpPCW_chevron {
141
+ color: var(--dsw-alias-label-secondary);
142
+ flex: none;
143
+ }
144
+
145
+ .WcpPCW_menuCopy {
146
+ flex-direction: column;
147
+ gap: 2px;
148
+ min-width: 220px;
149
+ display: flex;
150
+ }
151
+
152
+ .WcpPCW_menuTitle {
153
+ font-size: 13px;
154
+ line-height: 17px;
155
+ }
156
+
157
+ .WcpPCW_menuDetail {
158
+ white-space: normal;
159
+ max-width: 290px;
160
+ color: var(--dsw-alias-label-tertiary);
161
+ font-size: 11px;
162
+ line-height: 15px;
163
+ }
164
+
165
+ .WcpPCW_error {
166
+ color: var(--dsw-alias-state-error-primary);
167
+ padding: 4px 8px 0;
168
+ font-size: 11px;
169
+ line-height: 15px;
170
+ }
171
+
172
+ .WcpPCW_unavailable {
173
+ color: var(--dsw-alias-label-tertiary);
174
+ padding: 4px 8px 0;
175
+ font-size: 11px;
176
+ line-height: 15px;
177
+ }
178
+
179
+ .WcpPCW_settingsHint {
180
+ color: var(--dsw-alias-label-secondary);
181
+ padding: 4px 8px 0;
182
+ font-size: 11px;
183
+ line-height: 15px;
184
+ }
185
+
186
+ .WcpPCW_pricing {
187
+ color: var(--dsw-alias-label-tertiary);
188
+ padding: 4px 8px 0;
189
+ font-size: 11px;
190
+ line-height: 15px;
191
+ }
192
+
193
+ .WcpPCW_custom {
194
+ border-top: 1px solid var(--dsw-alias-border-l2);
195
+ margin-top: 16px;
196
+ padding: 16px 0 0;
197
+ }
198
+
199
+ .WcpPCW_customTitle {
200
+ margin: 0 0 6px;
201
+ font-size: 13px;
202
+ line-height: 17px;
203
+ }
204
+
205
+ .WcpPCW_customNote {
206
+ color: var(--dsw-alias-label-tertiary);
207
+ margin: 4px 0;
208
+ font-size: 11px;
209
+ line-height: 15px;
210
+ }
211
+
212
+ .WcpPCW_stage {
213
+ border: 0;
214
+ border-top: 1px solid var(--dsw-alias-border-l2);
215
+ margin: 12px 0 0;
216
+ padding: 12px 0 0;
217
+ }
218
+
219
+ .WcpPCW_stageToggle {
220
+ align-items: center;
221
+ gap: 6px;
222
+ font-size: 12px;
223
+ line-height: 16px;
224
+ display: inline-flex;
225
+ }
226
+
227
+ .WcpPCW_fieldGrid {
228
+ grid-template-columns: 1fr;
229
+ gap: 8px;
230
+ display: grid;
231
+ }
232
+
233
+ .WcpPCW_field {
234
+ min-width: 0;
235
+ color: var(--dsw-alias-label-secondary);
236
+ flex-direction: column;
237
+ gap: 4px;
238
+ margin-top: 8px;
239
+ font-size: 11px;
240
+ line-height: 15px;
241
+ display: flex;
242
+ }
243
+
244
+ .WcpPCW_field input, .WcpPCW_field select {
245
+ box-sizing: border-box;
246
+ border: 1px solid var(--dsw-alias-border-l2);
247
+ background: var(--dsw-alias-bg-base);
248
+ width: 100%;
249
+ min-width: 0;
250
+ min-height: 30px;
251
+ color: var(--dsw-alias-label-primary);
252
+ border-radius: 7px;
253
+ padding: 4px 6px;
254
+ }
255
+
256
+ .WcpPCW_field input:focus-visible, .WcpPCW_field select:focus-visible, .WcpPCW_actions button:focus-visible {
257
+ outline-offset: 2px;
258
+ outline: 2px solid;
259
+ }
260
+
261
+ .WcpPCW_actions {
262
+ flex-wrap: wrap;
263
+ gap: 8px;
264
+ margin-top: 10px;
265
+ display: flex;
266
+ }
267
+
268
+ .WcpPCW_actions button {
269
+ border: 1px solid var(--dsw-alias-border-l2);
270
+ min-height: 30px;
271
+ color: var(--dsw-alias-label-primary);
272
+ background: none;
273
+ border-radius: 7px;
274
+ padding: 4px 8px;
275
+ }
276
+
277
+ .WcpPCW_actions button:active:not(:disabled) {
278
+ transform: scale(.98);
279
+ }
280
+
281
+ .WcpPCW_actions button:disabled {
282
+ opacity: .6;
283
+ }
284
+
285
+ @media (width <= 560px) {
286
+ .WcpPCW_profileGrid {
287
+ grid-template-columns: 1fr;
288
+ }
289
+ }
290
+
291
+ .WcpPCW_autoCompact {
292
+ border: 1px solid #80808059;
293
+ border-radius: 8px;
294
+ margin-top: 24px;
295
+ padding: 16px;
296
+ }
297
+
298
+ .WcpPCW_autoCompactTitle {
299
+ margin: 0 0 8px;
300
+ font-size: 15px;
301
+ font-weight: 600;
302
+ }
303
+
304
+ .WcpPCW_autoCompactRisk {
305
+ opacity: .9;
306
+ margin: 8px 0 0;
307
+ font-size: 12px;
308
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "name": "dsh-context-compression-improved",
3
+ "version": "0.1.1",
4
+ "description": "One-install context-compression selector bundle for DeepSeek Harness",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./invariant": {
14
+ "types": "./lib/invariant.d.ts",
15
+ "default": "./lib/invariant.js"
16
+ },
17
+ "./pruner": {
18
+ "types": "./lib/pruner.d.ts",
19
+ "default": "./lib/pruner.js"
20
+ },
21
+ "./client": {
22
+ "types": "./lib/client.d.ts",
23
+ "default": "./lib/client.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib/**/*.js",
29
+ "lib/**/*.d.ts",
30
+ "cordis.patch.yml",
31
+ "dsh.plugin.json",
32
+ "screenshots.json",
33
+ "assets",
34
+ "README.md",
35
+ "README.zh.md",
36
+ "LICENSE",
37
+ "THIRD_PARTY_NOTICES.md"
38
+ ],
39
+ "dsh": {
40
+ "client": {
41
+ "inject": [
42
+ "@deepseek-ai/dsh-client-locale",
43
+ "@deepseek-ai/dsh-client-runtime",
44
+ "@deepseek-ai/dsh-client-ui-settings",
45
+ "@deepseek-ai/dsh-client-ui-workspace"
46
+ ],
47
+ "platform": "web"
48
+ },
49
+ "bundle": {
50
+ "patch": "./cordis.patch.yml"
51
+ }
52
+ },
53
+ "scripts": {
54
+ "bundle": "tsdown --config tsdown.config.ts && tsdown --config tsdown.client.config.ts",
55
+ "prepack": "pnpm run bundle",
56
+ "typecheck": "tsc --noEmit -p tsconfig.json",
57
+ "test": "vitest run --root ../.. --config vitest.config.ts --project runtime --project selector-host --project selector-client",
58
+ "pack:dry-run": "npm pack --dry-run"
59
+ },
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/drscrewdriver/dsh-context-compression-improved.git",
63
+ "directory": "packages/selector"
64
+ },
65
+ "homepage": "https://github.com/drscrewdriver/dsh-context-compression-improved#readme",
66
+ "bugs": {
67
+ "url": "https://github.com/drscrewdriver/dsh-context-compression-improved/issues"
68
+ },
69
+ "author": "WilliamShi666",
70
+ "engines": {
71
+ "node": "^22.19.0 || >=24.0.0",
72
+ "dsh": ">=0.1.1-rc.2 <0.2.0-0"
73
+ },
74
+ "keywords": [
75
+ "deepseek",
76
+ "harness",
77
+ "context",
78
+ "compression",
79
+ "plugin"
80
+ ],
81
+ "license": "MIT",
82
+ "publishConfig": {
83
+ "access": "public",
84
+ "tag": "latest"
85
+ },
86
+ "dependencies": {
87
+ "@huggingface/tokenizers": "0.1.3",
88
+ "js-yaml": "^4.3.2"
89
+ },
90
+ "peerDependencies": {
91
+ "@deepseek-ai/cordis": ">=4.0.1 <5",
92
+ "@deepseek-ai/cordis-plugin-include": ">=1.0.6 <2",
93
+ "@deepseek-ai/cordis-plugin-loader": ">=1.0.2 <2",
94
+ "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2 <0.2.0",
95
+ "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2 <0.2.0",
96
+ "@deepseek-ai/dsh-command-compact": ">=0.1.1-rc.2 <0.2.0",
97
+ "@deepseek-ai/dsh-compaction": ">=0.1.1-rc.2 <0.2.0",
98
+ "@deepseek-ai/dsh-compaction-basic": ">=0.1.1-rc.2 <0.2.0",
99
+ "@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.2 <0.2.0",
100
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2 <0.2.0",
101
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.1-rc.2 <0.2.0",
102
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.2 <0.2.0",
103
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.2 <0.2.0",
104
+ "@deepseek-ai/dsh-client-ui-workspace": ">=0.1.1-rc.2 <0.2.0",
105
+ "@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2 <0.2.0",
106
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2 <0.2.0",
107
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2 <0.2.0",
108
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2 <0.2.0",
109
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.1-rc.2 <0.2.0",
110
+ "@deepseek-ai/dsh-token-meter": ">=0.1.1-rc.2 <0.2.0",
111
+ "@deepseek-ai/dsh-tools": ">=0.1.1-rc.2 <0.2.0",
112
+ "@deepseek-ai/schemastery": ">=3.18.1 <4",
113
+ "react": ">=18 <19"
114
+ }
115
+ }
@@ -1,6 +1,6 @@
1
- {
2
- "screenshots": [
3
- "assets/screenshots/context-compression-selector-settings.png",
4
- "assets/screenshots/context-compression-selector-profiles.jpg"
5
- ]
6
- }
1
+ {
2
+ "screenshots": [
3
+ "assets/screenshots/context-compression-selector-settings.png",
4
+ "assets/screenshots/context-compression-selector-profiles.jpg"
5
+ ]
6
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * CompressionProfileControls: dropdown selector for compression profiles,
3
+ * plus AutoCompactThresholdControls and CodeSkeletonControls sub-components.
4
+ *
5
+ * Extracted from CompressionProfileSelector.tsx to reduce god-module size.
6
+ *
7
+ * @module dsh-context-compression-improved/client/CompressionProfileControls
8
+ */
9
+
10
+ import { useEffect, useId, useState } from 'react'
11
+ import { IconChevronDownOutline14, Menu } from '@deepseek-ai/dsh-client-ui-primitives'
12
+ import type { ContextCompressionLocaleKey } from './locales.ts'
13
+ import css from './CompressionProfileSelector.module.css'
14
+ import {
15
+ AUTO_COMPACT_THRESHOLD_LIMITS,
16
+ COMPRESSION_PROFILES,
17
+ isValidAutoCompactThresholdPercent,
18
+ type CompressionProfile,
19
+ type CustomCompressionPolicyV3,
20
+ } from '../profiles.ts'
21
+ import type { CompressionProfileSelectorProps } from './CompressionProfileSelector.tsx'
22
+ import { CustomPolicyEditor, editableCustom } from './CustomPolicyEditor.tsx'
23
+
24
+ export function CompressionProfileControls({
25
+ useCompression, useSessions, select, saveCustom, resetCustom, t, showCustomEditor,
26
+ }: CompressionProfileSelectorProps & { showCustomEditor: boolean }) {
27
+ const state = useCompression(snapshot => snapshot)
28
+ const currentPreset = useSessions((sessions) => {
29
+ const current = sessions.current
30
+ return current === undefined ? undefined : sessions.byId[current]?.agentPreset
31
+ })
32
+ // A profile is Host-global. A cold Session may not have reported a live
33
+ // capability yet, so only Minimal blocks initial configuration.
34
+ const contextCompressionAvailable = currentPreset !== 'minimal'
35
+ const [open, setOpen] = useState(false)
36
+ const [saving, setSaving] = useState(false)
37
+ const [saveError, setSaveError] = useState<string | null>(null)
38
+ const [draft, setDraft] = useState<CustomCompressionPolicyV3 | null>(null)
39
+ const unavailableId = useId()
40
+ const current = state.value?.profile ?? 'balanced'
41
+ useEffect(() => {
42
+ if (state.status === 'ready' && state.writable && contextCompressionAvailable) return
43
+ setOpen(false)
44
+ }, [contextCompressionAvailable, state.status, state.writable])
45
+ useEffect(() => {
46
+ const custom = state.value?.custom
47
+ setDraft(current === 'custom' && custom !== undefined ? editableCustom(custom) : null)
48
+ }, [current, state.value?.custom])
49
+ if (state.status === 'unavailable') return null
50
+ const busy = state.status === 'loading' || saving
51
+ const label = busy ? t('status.loading') : t(`profile.${current}`)
52
+ return (
53
+ <div className={css.root}>
54
+ <Menu
55
+ open={open && contextCompressionAvailable}
56
+ onClose={() => { setOpen(false) }}
57
+ selectedId={current}
58
+ items={COMPRESSION_PROFILES.map(profile => ({
59
+ id: profile,
60
+ label: (
61
+ <span className={css.menuCopy}>
62
+ <span className={css.menuTitle}>{t(`profile.${profile}`)}</span>
63
+ <span className={css.menuDetail}>{t(`detail.${profile}`)}</span>
64
+ </span>
65
+ ),
66
+ }))}
67
+ onSelect={(id) => {
68
+ setOpen(false)
69
+ if (id === current || !COMPRESSION_PROFILES.includes(id as CompressionProfile)) return
70
+ setSaveError(null)
71
+ setSaving(true)
72
+ void select(id as CompressionProfile).then(
73
+ () => { setSaving(false) },
74
+ (error: unknown) => {
75
+ setSaving(false)
76
+ setSaveError(error instanceof Error && error.message !== '' ? error.message : t('status.saveFailed'))
77
+ },
78
+ )
79
+ }}
80
+ align="start"
81
+ portal
82
+ anchor={(
83
+ <button
84
+ type="button"
85
+ className={css.button}
86
+ aria-haspopup="menu"
87
+ aria-expanded={open && contextCompressionAvailable}
88
+ aria-describedby={contextCompressionAvailable ? undefined : unavailableId}
89
+ disabled={busy || !state.writable || !contextCompressionAvailable}
90
+ onClick={() => { setOpen(value => !value) }}
91
+ >
92
+ <span className={css.copy}>
93
+ <span className={css.label}>{t('label')}</span>
94
+ <span className={css.value}>{label}</span>
95
+ </span>
96
+ <IconChevronDownOutline14 className={css.chevron} />
97
+ </button>
98
+ )}
99
+ />
100
+ {contextCompressionAvailable ? null : (
101
+ <div id={unavailableId} className={css.unavailable} role="status">
102
+ {t('status.minimalUnavailable')}
103
+ </div>
104
+ )}
105
+ <div className={css.pricing}>{t('pricing.disclosure')}</div>
106
+ <div className={css.settingsHint}>
107
+ {t('autoCompact.summaryHint').replace('{percent}', String(state.value?.autoCompact?.thresholdPercent ?? AUTO_COMPACT_THRESHOLD_LIMITS.default))}
108
+ </div>
109
+ {current !== 'custom' || showCustomEditor ? null : (
110
+ <div className={css.settingsHint}>{t('custom.settingsHint')}</div>
111
+ )}
112
+ {current !== 'custom' || draft === null || !showCustomEditor ? null : (
113
+ <CustomPolicyEditor
114
+ value={draft}
115
+ disabled={busy || !state.writable || !contextCompressionAvailable}
116
+ setValue={setDraft}
117
+ save={() => saveCustom(structuredClone(draft))}
118
+ reset={resetCustom}
119
+ settle={(operation) => {
120
+ setSaveError(null)
121
+ setSaving(true)
122
+ void operation().then(
123
+ () => { setSaving(false) },
124
+ (error: unknown) => {
125
+ setSaving(false)
126
+ setSaveError(error instanceof Error && error.message !== '' ? error.message : t('status.saveFailed'))
127
+ },
128
+ )
129
+ }}
130
+ t={t}
131
+ />
132
+ )}
133
+ {saveError === null ? null : <div className={css.error} role="alert">{saveError}</div>}
134
+ </div>
135
+ )
136
+ }
137
+
138
+ interface AutoCompactThresholdControlsProps {
139
+ value: number
140
+ disabled: boolean
141
+ save: (thresholdPercent: number) => Promise<void>
142
+ settle: (operation: () => Promise<void>) => void
143
+ t: (key: ContextCompressionLocaleKey) => string
144
+ }
145
+
146
+ /**
147
+ * The authoritative Auto Compact threshold editor for the context-compression
148
+ * section. A typed number input and its save path are kept deliberately simple;
149
+ * values outside the recommended 70–85 band warn without blocking.
150
+ */
151
+ export function AutoCompactThresholdControls({ value, disabled, save, settle, t }: AutoCompactThresholdControlsProps) {
152
+ const [draft, setDraft] = useState(String(value))
153
+ useEffect(() => { setDraft(String(value)) }, [value])
154
+ const parsed = Number(draft)
155
+ // Number inputs legally produce value-equivalent drafts such as '8e1';
156
+ // only the parsed value needs to be a valid threshold.
157
+ const valid = isValidAutoCompactThresholdPercent(parsed)
158
+ const risk = !valid
159
+ ? 'autoCompact.invalid'
160
+ : parsed < 70 ? 'autoCompact.riskLow'
161
+ : parsed > 85 ? 'autoCompact.riskHigh'
162
+ : undefined
163
+ return (
164
+ <section className={css.autoCompact} aria-labelledby="context-compression-autocompact-title">
165
+ <h3 id="context-compression-autocompact-title" className={css.autoCompactTitle}>{t('autoCompact.title')}</h3>
166
+ <p className={css.customNote}>{t('autoCompact.description')}</p>
167
+ <label className={css.field}>
168
+ <span>{t('autoCompact.inputLabel')}</span>
169
+ <input
170
+ type="number"
171
+ value={draft}
172
+ min={AUTO_COMPACT_THRESHOLD_LIMITS.min}
173
+ max={AUTO_COMPACT_THRESHOLD_LIMITS.max}
174
+ step={AUTO_COMPACT_THRESHOLD_LIMITS.step}
175
+ disabled={disabled}
176
+ aria-invalid={!valid}
177
+ onChange={(event) => { setDraft(event.currentTarget.value) }}
178
+ />
179
+ </label>
180
+ {risk === undefined ? null : (
181
+ <div className={risk === 'autoCompact.invalid' ? css.error : css.autoCompactRisk} role={risk === 'autoCompact.invalid' ? 'alert' : 'note'}>
182
+ {t(risk)}
183
+ </div>
184
+ )}
185
+ <div className={css.actions}>
186
+ <button
187
+ type="button"
188
+ disabled={disabled || !valid || parsed === value}
189
+ onClick={() => { settle(() => save(parsed)) }}
190
+ >
191
+ {t('autoCompact.save')}
192
+ </button>
193
+ </div>
194
+ </section>
195
+ )
196
+ }
197
+
198
+ interface CodeSkeletonControlsProps {
199
+ value: boolean
200
+ disabled: boolean
201
+ save: (enabled: boolean) => Promise<void>
202
+ settle: (operation: () => Promise<void>) => void
203
+ t: (key: ContextCompressionLocaleKey) => string
204
+ }
205
+
206
+ /**
207
+ * The authoritative code-skeleton reducer gate for the context-compression
208
+ * section. Deliberately minimal — an on/off select plus its own save path —
209
+ * because the gate is orthogonal to every profile and carries no parameters.
210
+ */
211
+ export function CodeSkeletonControls({ value, disabled, save, settle, t }: CodeSkeletonControlsProps) {
212
+ return (
213
+ <section className={css.autoCompact} aria-labelledby="context-compression-codeskeleton-title">
214
+ <h3 id="context-compression-codeskeleton-title" className={css.autoCompactTitle}>{t('codeSkeleton.title')}</h3>
215
+ <p className={css.customNote}>{t('codeSkeleton.description')}</p>
216
+ <label className={css.field}>
217
+ <span>{t('codeSkeleton.enabled')}</span>
218
+ <select
219
+ value={value ? 'on' : 'off'}
220
+ disabled={disabled}
221
+ onChange={(event) => { settle(() => save(event.currentTarget.value === 'on')) }}
222
+ >
223
+ <option value="on">{t('codeSkeleton.enabled.on')}</option>
224
+ <option value="off">{t('codeSkeleton.enabled.off')}</option>
225
+ </select>
226
+ </label>
227
+ </section>
228
+ )
229
+ }