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.
- package/.gitattributes +1 -0
- package/.github/workflows/ci.yml +39 -0
- package/CHANGELOG.ja.md +39 -0
- package/CHANGELOG.ko.md +39 -0
- package/CHANGELOG.md +135 -0
- package/CHANGELOG.zh.md +39 -0
- package/CONTRIBUTING.md +22 -0
- package/README.ja.md +104 -0
- package/README.ko.md +103 -0
- package/README.md +89 -12
- package/README.zh.md +87 -12
- package/SECURITY.md +18 -0
- package/THIRD_PARTY_NOTICES.md +7 -31
- package/docs/installation.ja.md +76 -0
- package/docs/installation.ko.md +76 -0
- package/docs/installation.md +76 -0
- package/docs/installation.zh.md +76 -0
- package/docs/repair-log.md +582 -0
- package/eslint.config.js +30 -0
- package/package.json +85 -82
- package/packages/selector/LICENSE +21 -0
- package/packages/selector/README.md +26 -0
- package/packages/selector/README.zh.md +26 -0
- package/packages/selector/THIRD_PARTY_NOTICES.md +38 -0
- package/packages/selector/docs/history-tool-call-working-set-spec.md +112 -0
- package/packages/selector/docs/native-tool-result-selector-spec.md +34 -0
- package/packages/selector/docs/subagent-cache-reuse-spec.md +46 -0
- package/packages/selector/lib/style.css +308 -0
- package/packages/selector/package.json +115 -0
- package/{screenshots.json → packages/selector/screenshots.json} +6 -6
- package/packages/selector/src/client/CompressionProfileControls.tsx +229 -0
- package/packages/selector/src/client/CompressionProfileSelector.module.css +170 -0
- package/packages/selector/src/client/CompressionProfileSelector.tsx +79 -0
- package/packages/selector/src/client/CustomPolicyEditor.tsx +216 -0
- package/packages/selector/src/client/EstimatorControls.tsx +281 -0
- package/packages/selector/src/client/decode.ts +49 -0
- package/packages/selector/src/client/index.ts +111 -0
- package/packages/selector/src/client/locales.ts +198 -0
- package/packages/selector/src/client/preset-options.ts +70 -0
- package/packages/selector/src/client/settings-section.tsx +126 -0
- package/packages/selector/src/css-modules.d.ts +6 -0
- package/packages/selector/src/deepseek-v4-tokenizer.ts +210 -0
- package/packages/selector/src/estimator-catalog.ts +104 -0
- package/packages/selector/src/index.ts +327 -0
- package/packages/selector/src/invariant.ts +113 -0
- package/packages/selector/src/preset-overlay.ts +567 -0
- package/packages/selector/src/profiles.ts +342 -0
- package/packages/selector/src/pruner/content.ts +188 -0
- package/packages/selector/src/pruner/session.ts +94 -0
- package/packages/selector/src/pruner/state.ts +43 -0
- package/packages/selector/src/pruner/tuning.ts +23 -0
- package/packages/selector/src/pruner/types.ts +60 -0
- package/packages/selector/src/pruner.ts +2144 -0
- package/packages/selector/src/runtime/adaptive-cost.ts +194 -0
- package/packages/selector/src/runtime/audit.ts +215 -0
- package/packages/selector/src/runtime/config.ts +613 -0
- package/packages/selector/src/runtime/custom-policy.ts +278 -0
- package/packages/selector/src/runtime/deepseek-official-pricing.ts +298 -0
- package/packages/selector/src/runtime/deepseek-v4-vision-tokens.ts +254 -0
- package/packages/selector/src/runtime/measurement.ts +403 -0
- package/packages/selector/src/runtime/reducers.ts +656 -0
- package/packages/selector/src/runtime/retrieve.ts +457 -0
- package/packages/selector/src/runtime/session-events.ts +17 -0
- package/packages/selector/src/runtime/tail-trim.ts +166 -0
- package/packages/selector/src/runtime/token-count.ts +72 -0
- package/packages/selector/src/runtime/tokenpilot/dedup.ts +81 -0
- package/packages/selector/src/runtime/tokenpilot/estimator.ts +183 -0
- package/packages/selector/src/runtime/tokenpilot/locator.ts +128 -0
- package/packages/selector/src/runtime/tokenpilot/read-state.ts +77 -0
- package/packages/selector/src/runtime/types.ts +309 -0
- package/packages/selector/src/runtime/value.ts +48 -0
- package/packages/selector/tests/auto-compact.client.spec.tsx +226 -0
- package/packages/selector/tests/built/client-artifact.spec.ts +51 -0
- package/packages/selector/tests/cache-prefix-audit.spec.ts +123 -0
- package/packages/selector/tests/code-skeleton.client.spec.ts +88 -0
- package/packages/selector/tests/custom-contract.client.spec.ts +202 -0
- package/packages/selector/tests/estimator-catalog.spec.ts +70 -0
- package/packages/selector/tests/estimator-channel.client.spec.tsx +247 -0
- package/packages/selector/tests/estimator-route-registration.host.spec.ts +176 -0
- package/packages/selector/tests/host-preset-overlay.host.spec.ts +204 -0
- package/packages/selector/tests/preset-options-write.client.spec.ts +181 -0
- package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +196 -0
- package/packages/selector/tests/preset-overlay.host.spec.ts +243 -0
- package/packages/selector/tests/profiles.client.spec.tsx +434 -0
- package/packages/selector/tests/public/package-contract.client.spec.ts +33 -0
- package/packages/selector/tests/runtime/adaptive-cost.spec.ts +167 -0
- package/packages/selector/tests/runtime/audit.spec.ts +129 -0
- package/packages/selector/tests/runtime/auto-compact-config.spec.ts +523 -0
- package/packages/selector/tests/runtime/code-skeleton.spec.ts +141 -0
- package/packages/selector/tests/runtime/deepseek-official-pricing.spec.ts +186 -0
- package/packages/selector/tests/runtime/deepseek-v4-tokenizer.spec.ts +122 -0
- package/packages/selector/tests/runtime/deepseek-v4-vision-tokens.spec.ts +122 -0
- package/packages/selector/tests/runtime/fixtures/profile-baseline.json +273 -0
- package/packages/selector/tests/runtime/fixtures/tokenizer-golden.json +106 -0
- package/packages/selector/tests/runtime/fixtures/vision-golden.json +459 -0
- package/packages/selector/tests/runtime/public/public-runtime.spec.ts +2531 -0
- package/packages/selector/tests/runtime/session-events.spec.ts +27 -0
- package/packages/selector/tests/runtime/tokenizer-golden.spec.ts +53 -0
- package/packages/selector/tests/runtime/tokenpilot/dedup.spec.ts +52 -0
- package/packages/selector/tests/runtime/tokenpilot/estimator.spec.ts +56 -0
- package/packages/selector/tests/runtime/tokenpilot/locator.spec.ts +76 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +100 -0
- package/packages/selector/tests/runtime/tokenpilot/read-state.spec.ts +58 -0
- package/packages/selector/tests/runtime/value.spec.ts +23 -0
- package/packages/selector/tests/standing-generation.host.spec.ts +631 -0
- package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +250 -0
- package/packages/selector/tests/support/cache-prefix-audit.ts +105 -0
- package/packages/selector/tests/support/mock-adapter.ts +37 -0
- package/packages/selector/tests/support/ui-primitives.tsx +34 -0
- package/packages/selector/tsconfig.json +11 -0
- package/packages/selector/tsdown.client.config.ts +102 -0
- package/packages/selector/tsdown.config.ts +20 -0
- package/pnpm-workspace.yaml +19 -0
- package/scripts/capture-profile-baseline.ts +80 -0
- package/scripts/generate-tokenizer-fixtures.py +81 -0
- package/scripts/generate-vision-fixtures.py +208 -0
- package/scripts/packed-components-smoke.ts +713 -0
- package/scripts/packed-install-e2e.ts +1072 -0
- package/scripts/verify-release.ts +300 -0
- package/tests/TEST_INVENTORY.md +42 -0
- package/tsconfig.base.json +18 -0
- package/tsconfig.json +7 -0
- package/tsconfig.scripts.json +13 -0
- package/tsconfig.tests.json +15 -0
- package/vitest.built.config.ts +9 -0
- package/vitest.config.ts +43 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/LICENSE.DeepSeek-V4-Pro.txt +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/manifest.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer_config.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/LICENSE.DeepSeek-V4-Flash-Vision-Exp.txt +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/manifest.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer_config.json +0 -0
- /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-profiles.jpg +0 -0
- /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-settings.png +0 -0
- /package/{cordis.patch.yml → packages/selector/cordis.patch.yml} +0 -0
- /package/{dsh.plugin.json → packages/selector/dsh.plugin.json} +0 -0
- /package/{lib → packages/selector/lib}/client.d.ts +0 -0
- /package/{lib → packages/selector/lib}/client.js +0 -0
- /package/{lib → packages/selector/lib}/config.js +0 -0
- /package/{lib → packages/selector/lib}/index.d.ts +0 -0
- /package/{lib → packages/selector/lib}/index.js +0 -0
- /package/{lib → packages/selector/lib}/invariant.d.ts +0 -0
- /package/{lib → packages/selector/lib}/invariant.js +0 -0
- /package/{lib → packages/selector/lib}/pruner.d.ts +0 -0
- /package/{lib → packages/selector/lib}/pruner.js +0 -0
- /package/{lib → packages/selector/lib}/tail-trim.js +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
.settingsSection {
|
|
2
|
+
display: flex;
|
|
3
|
+
max-width: 720px;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 12px;
|
|
6
|
+
color: var(--dsw-alias-label-primary);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.settingsTitle {
|
|
10
|
+
margin: 0;
|
|
11
|
+
color: var(--dsw-alias-label-primary);
|
|
12
|
+
font-size: 16px;
|
|
13
|
+
font-weight: 500;
|
|
14
|
+
line-height: 24px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.settingsDescription {
|
|
18
|
+
margin: 0;
|
|
19
|
+
color: var(--dsw-alias-label-tertiary);
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
line-height: 22px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.root {
|
|
25
|
+
width: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.profileGrid {
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
31
|
+
gap: 10px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.profileCard {
|
|
35
|
+
min-height: 112px;
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: 8px;
|
|
39
|
+
padding: 14px;
|
|
40
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
41
|
+
border-radius: 10px;
|
|
42
|
+
background: transparent;
|
|
43
|
+
color: var(--dsw-alias-label-primary);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
text-align: left;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.profileCard:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
49
|
+
.profileCard[aria-pressed="true"] { border-color: var(--dsw-alias-label-primary); }
|
|
50
|
+
.profileCard:active:not(:disabled) { transform: scale(.99); }
|
|
51
|
+
.profileCard:disabled { cursor: default; opacity: .6; }
|
|
52
|
+
.profileCard:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
|
|
53
|
+
.profileCardTop { display: flex; align-items: center; gap: 8px; }
|
|
54
|
+
.profileCardTitle { font-size: 14px; font-weight: 500; line-height: 20px; }
|
|
55
|
+
.profileCurrent { padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-base); font-size: 10px; line-height: 14px; }
|
|
56
|
+
.profileCardDetail { color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; }
|
|
57
|
+
|
|
58
|
+
.button {
|
|
59
|
+
width: 100%;
|
|
60
|
+
min-height: 34px;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
padding: 6px 8px;
|
|
65
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
66
|
+
border-radius: 10px;
|
|
67
|
+
background: transparent;
|
|
68
|
+
color: var(--dsw-alias-label-primary);
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
text-align: left;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.button:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
74
|
+
.button:disabled { cursor: default; opacity: .6; }
|
|
75
|
+
|
|
76
|
+
.copy { flex: 1; min-width: 0; }
|
|
77
|
+
.label { font-size: 11px; line-height: 14px; color: var(--dsw-alias-label-tertiary); }
|
|
78
|
+
.value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; line-height: 17px; }
|
|
79
|
+
.chevron { flex: none; color: var(--dsw-alias-label-secondary); }
|
|
80
|
+
|
|
81
|
+
.menuCopy { display: flex; min-width: 220px; flex-direction: column; gap: 2px; }
|
|
82
|
+
.menuTitle { font-size: 13px; line-height: 17px; }
|
|
83
|
+
.menuDetail { max-width: 290px; white-space: normal; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
84
|
+
|
|
85
|
+
.error {
|
|
86
|
+
padding: 4px 8px 0;
|
|
87
|
+
color: var(--dsw-alias-state-error-primary);
|
|
88
|
+
font-size: 11px;
|
|
89
|
+
line-height: 15px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.unavailable {
|
|
93
|
+
padding: 4px 8px 0;
|
|
94
|
+
color: var(--dsw-alias-label-tertiary);
|
|
95
|
+
font-size: 11px;
|
|
96
|
+
line-height: 15px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.settingsHint {
|
|
100
|
+
padding: 4px 8px 0;
|
|
101
|
+
color: var(--dsw-alias-label-secondary);
|
|
102
|
+
font-size: 11px;
|
|
103
|
+
line-height: 15px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.pricing {
|
|
107
|
+
padding: 4px 8px 0;
|
|
108
|
+
color: var(--dsw-alias-label-tertiary);
|
|
109
|
+
font-size: 11px;
|
|
110
|
+
line-height: 15px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.custom {
|
|
114
|
+
margin-top: 16px;
|
|
115
|
+
padding: 16px 0 0;
|
|
116
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.customTitle { margin: 0 0 6px; font-size: 13px; line-height: 17px; }
|
|
120
|
+
.customNote { margin: 4px 0; color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 15px; }
|
|
121
|
+
|
|
122
|
+
.stage {
|
|
123
|
+
margin: 12px 0 0;
|
|
124
|
+
padding: 12px 0 0;
|
|
125
|
+
border: 0;
|
|
126
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.stageToggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; line-height: 16px; }
|
|
130
|
+
.fieldGrid { display: grid; grid-template-columns: 1fr; gap: 8px; }
|
|
131
|
+
.field { display: flex; min-width: 0; flex-direction: column; gap: 4px; margin-top: 8px; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 15px; }
|
|
132
|
+
.field input, .field select {
|
|
133
|
+
width: 100%;
|
|
134
|
+
min-width: 0;
|
|
135
|
+
min-height: 30px;
|
|
136
|
+
box-sizing: border-box;
|
|
137
|
+
padding: 4px 6px;
|
|
138
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
139
|
+
border-radius: 7px;
|
|
140
|
+
background: var(--dsw-alias-bg-base);
|
|
141
|
+
color: var(--dsw-alias-label-primary);
|
|
142
|
+
}
|
|
143
|
+
.field input:focus-visible, .field select:focus-visible, .actions button:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
|
|
144
|
+
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
145
|
+
.actions button { min-height: 30px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 7px; background: transparent; color: var(--dsw-alias-label-primary); }
|
|
146
|
+
.actions button:active:not(:disabled) { transform: scale(.98); }
|
|
147
|
+
.actions button:disabled { opacity: .6; }
|
|
148
|
+
|
|
149
|
+
@media (max-width: 560px) {
|
|
150
|
+
.profileGrid { grid-template-columns: 1fr; }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.autoCompact {
|
|
154
|
+
margin-top: 24px;
|
|
155
|
+
padding: 16px;
|
|
156
|
+
border: 1px solid rgba(128, 128, 128, 0.35);
|
|
157
|
+
border-radius: 8px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.autoCompactTitle {
|
|
161
|
+
margin: 0 0 8px;
|
|
162
|
+
font-size: 15px;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.autoCompactRisk {
|
|
167
|
+
margin: 8px 0 0;
|
|
168
|
+
font-size: 12px;
|
|
169
|
+
opacity: 0.9;
|
|
170
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CompressionProfileSelector — thin re-export hub.
|
|
3
|
+
*
|
|
4
|
+
* Component implementations now live in dedicated modules to keep the
|
|
5
|
+
* god-module under control. This file preserves the public API surface so
|
|
6
|
+
* existing consumers see no change.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-context-compression-improved/client/CompressionProfileSelector
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'
|
|
12
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
13
|
+
import {
|
|
14
|
+
COMPRESSION_PROFILES,
|
|
15
|
+
isCustomCompressionPolicy,
|
|
16
|
+
type CompressionProfile,
|
|
17
|
+
type CustomCompressionPolicy,
|
|
18
|
+
type ContextCompressionSettings,
|
|
19
|
+
type PresetOptionsSettings,
|
|
20
|
+
} from '../profiles.ts'
|
|
21
|
+
|
|
22
|
+
export { COMPRESSION_PROFILES, isCustomCompressionPolicy }
|
|
23
|
+
export type { CompressionProfile, CustomCompressionPolicy, ContextCompressionSettings, PresetOptionsSettings }
|
|
24
|
+
|
|
25
|
+
export interface CompressionSelectorInjected {
|
|
26
|
+
hooks: { compression: SettingsScope<ContextCompressionSettings> }
|
|
27
|
+
select: (profile: CompressionProfile) => Promise<void>
|
|
28
|
+
saveCustom: (custom: CustomCompressionPolicy) => Promise<void>
|
|
29
|
+
resetCustom: () => Promise<void>
|
|
30
|
+
saveAutoCompact: (thresholdPercent: number) => Promise<void>
|
|
31
|
+
saveCodeSkeleton: (enabled: boolean) => Promise<void>
|
|
32
|
+
/** Patch of `presetOptions` members; an explicit `undefined` clears that field. */
|
|
33
|
+
savePresetOptions: (options: import('./preset-options.ts').PresetOptionsPatch) => Promise<void>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type CompressionProfileSelectorProps =
|
|
37
|
+
PropsRuntime<'settings.section'>
|
|
38
|
+
& PropsLocale<'context-compression'>
|
|
39
|
+
& InjectFace<CompressionSelectorInjected>
|
|
40
|
+
|
|
41
|
+
// Re-export sub-components from dedicated modules.
|
|
42
|
+
export {
|
|
43
|
+
ContextCompressionSettingsSection,
|
|
44
|
+
SettingsCompressionProfileControls,
|
|
45
|
+
} from './settings-section.tsx'
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
CompressionProfileControls,
|
|
49
|
+
AutoCompactThresholdControls,
|
|
50
|
+
CodeSkeletonControls,
|
|
51
|
+
} from './CompressionProfileControls.tsx'
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
EstimatorControls,
|
|
55
|
+
EstimatorInactiveNotice,
|
|
56
|
+
ESTIMATOR_CATALOG_ROUTES,
|
|
57
|
+
} from './EstimatorControls.tsx'
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
CustomPolicyEditor,
|
|
61
|
+
StageFields,
|
|
62
|
+
editableCustom,
|
|
63
|
+
} from './CustomPolicyEditor.tsx'
|
|
64
|
+
|
|
65
|
+
// Imported for the wrapper component below; re-exported above.
|
|
66
|
+
import { CompressionProfileControls } from './CompressionProfileControls.tsx'
|
|
67
|
+
|
|
68
|
+
/** Full-page Settings surface backed by the same durable selector state. */
|
|
69
|
+
export function CompressionProfileSelector({
|
|
70
|
+
...props
|
|
71
|
+
}: CompressionProfileSelectorProps) {
|
|
72
|
+
return <CompressionProfileControls {...props} showCustomEditor={false} />
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
76
|
+
interface LocaleNamespaceMap {
|
|
77
|
+
'context-compression': import('./locales.ts').ContextCompressionLocaleKey
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CustomPolicyEditor: full custom compression policy editor with StageFields.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from CompressionProfileSelector.tsx to reduce god-module size.
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-context-compression-improved/client/CustomPolicyEditor
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ContextCompressionLocaleKey } from './locales.ts'
|
|
10
|
+
import css from './CompressionProfileSelector.module.css'
|
|
11
|
+
import {
|
|
12
|
+
isCustomCompressionPolicy,
|
|
13
|
+
type CustomCompressionBudget,
|
|
14
|
+
type CustomCompressionPolicy,
|
|
15
|
+
type CustomCompressionPolicyV3,
|
|
16
|
+
type CustomHistoryPolicy,
|
|
17
|
+
} from '../profiles.ts'
|
|
18
|
+
|
|
19
|
+
interface CustomPolicyEditorProps {
|
|
20
|
+
value: CustomCompressionPolicyV3
|
|
21
|
+
disabled: boolean
|
|
22
|
+
setValue: (value: CustomCompressionPolicyV3) => void
|
|
23
|
+
save: () => Promise<void>
|
|
24
|
+
reset: () => Promise<void>
|
|
25
|
+
settle: (operation: () => Promise<void>) => void
|
|
26
|
+
t: (key: ContextCompressionLocaleKey) => string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function CustomPolicyEditor({ value, disabled, setValue, save, reset, settle, t }: CustomPolicyEditorProps) {
|
|
30
|
+
const valid = isCustomCompressionPolicy(value)
|
|
31
|
+
const unitStep = value.unit === 'tokens' ? 1 : 0.01
|
|
32
|
+
const unitMax = value.unit === 'tokens' ? undefined : 100
|
|
33
|
+
const unitBounds = unitMax === undefined ? {} : { max: unitMax }
|
|
34
|
+
const setBudget = (
|
|
35
|
+
stage: 'fresh' | 'aggregate',
|
|
36
|
+
patch: Partial<CustomCompressionBudget>,
|
|
37
|
+
): void => {
|
|
38
|
+
setValue({ ...value, [stage]: { ...value[stage], ...patch } })
|
|
39
|
+
}
|
|
40
|
+
const setHistory = (patch: Partial<CustomHistoryPolicy>): void => {
|
|
41
|
+
setValue({ ...value, history: { ...value.history, ...patch } })
|
|
42
|
+
}
|
|
43
|
+
return (
|
|
44
|
+
<section className={css.custom} aria-labelledby="context-compression-custom-title">
|
|
45
|
+
<h3 id="context-compression-custom-title" className={css.customTitle}>{t('custom.title')}</h3>
|
|
46
|
+
<p className={css.customNote}>{t('custom.sessionScope')}</p>
|
|
47
|
+
<p className={css.customNote}>{t('custom.measurement')}</p>
|
|
48
|
+
<label className={css.field}>
|
|
49
|
+
<span>{t('custom.unit')}</span>
|
|
50
|
+
<select
|
|
51
|
+
value={value.unit}
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
onChange={(event) => {
|
|
54
|
+
setValue({
|
|
55
|
+
...value,
|
|
56
|
+
unit: event.currentTarget.value as CustomCompressionPolicy['unit'],
|
|
57
|
+
})
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<option value="tokens">{t('custom.unit.tokens')}</option>
|
|
61
|
+
<option value="context-percent">{t('custom.unit.contextPercent')}</option>
|
|
62
|
+
</select>
|
|
63
|
+
</label>
|
|
64
|
+
<StageFields
|
|
65
|
+
t={t}
|
|
66
|
+
title={t('custom.fresh.enabled')}
|
|
67
|
+
enabled={value.fresh.enabled}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
onEnabled={(enabled) => { setBudget('fresh', { enabled }) }}
|
|
70
|
+
fields={[
|
|
71
|
+
{ label: t('custom.fresh.trigger'), value: value.fresh.trigger, set: (trigger) => { setBudget('fresh', { trigger }) }, ...unitBounds },
|
|
72
|
+
{ label: t('custom.fresh.target'), value: value.fresh.target, set: (target) => { setBudget('fresh', { target }) }, ...unitBounds },
|
|
73
|
+
]}
|
|
74
|
+
step={unitStep}
|
|
75
|
+
/>
|
|
76
|
+
<StageFields
|
|
77
|
+
t={t}
|
|
78
|
+
title={t('custom.aggregate.enabled')}
|
|
79
|
+
enabled={value.aggregate.enabled}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
onEnabled={(enabled) => { setBudget('aggregate', { enabled }) }}
|
|
82
|
+
fields={[
|
|
83
|
+
{ label: t('custom.aggregate.trigger'), value: value.aggregate.trigger, set: (trigger) => { setBudget('aggregate', { trigger }) }, ...unitBounds },
|
|
84
|
+
{ label: t('custom.aggregate.target'), value: value.aggregate.target, set: (target) => { setBudget('aggregate', { target }) }, ...unitBounds },
|
|
85
|
+
]}
|
|
86
|
+
step={unitStep}
|
|
87
|
+
/>
|
|
88
|
+
<StageFields
|
|
89
|
+
t={t}
|
|
90
|
+
title={t('custom.history.enabled')}
|
|
91
|
+
enabled={value.history.enabled}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
onEnabled={(enabled) => { setHistory({ enabled }) }}
|
|
94
|
+
fields={[
|
|
95
|
+
{ label: t('custom.history.trigger'), value: value.history.trigger, set: (trigger) => { setHistory({ trigger }) }, ...unitBounds },
|
|
96
|
+
{ label: t('custom.history.keepRecentToolCalls'), value: value.history.keepRecentToolCalls, set: (keepRecentToolCalls) => { setHistory({ keepRecentToolCalls }) }, integer: true, allowZero: true },
|
|
97
|
+
{ label: t('custom.history.keepRecentTokens'), value: value.history.keepRecentTokens, set: (keepRecentTokens) => { setHistory({ keepRecentTokens }) }, allowZero: true, ...unitBounds },
|
|
98
|
+
{ label: t('custom.history.minReclaim'), value: value.history.minReclaim, set: (minReclaim) => { setHistory({ minReclaim }) }, ...unitBounds },
|
|
99
|
+
]}
|
|
100
|
+
step={unitStep}
|
|
101
|
+
fieldsEnabled={value.history.enabled || value.tailTrim.enabled}
|
|
102
|
+
/>
|
|
103
|
+
<label className={css.field}>
|
|
104
|
+
<span>{t('custom.prefixPolicy')}</span>
|
|
105
|
+
<select
|
|
106
|
+
value={value.prefixPolicy}
|
|
107
|
+
disabled={disabled || !value.history.enabled}
|
|
108
|
+
onChange={(event) => {
|
|
109
|
+
setValue({
|
|
110
|
+
...value,
|
|
111
|
+
prefixPolicy: event.currentTarget.value as CustomCompressionPolicy['prefixPolicy'],
|
|
112
|
+
})
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<option value="preserve">{t('custom.prefixPolicy.preserve')}</option>
|
|
116
|
+
<option value="pressure-break">{t('custom.prefixPolicy.pressureBreak')}</option>
|
|
117
|
+
</select>
|
|
118
|
+
</label>
|
|
119
|
+
<div className={css.customNote}>{t('custom.experimental')}</div>
|
|
120
|
+
<StageFields
|
|
121
|
+
t={t}
|
|
122
|
+
title={t('custom.tailTrim.enabled')}
|
|
123
|
+
enabled={value.tailTrim.enabled}
|
|
124
|
+
disabled={disabled}
|
|
125
|
+
onEnabled={(enabled) => {
|
|
126
|
+
setValue({ ...value, tailTrim: { ...value.tailTrim, enabled } })
|
|
127
|
+
}}
|
|
128
|
+
fields={[{
|
|
129
|
+
label: t('custom.tailTrim.trigger'),
|
|
130
|
+
value: value.tailTrim.trigger,
|
|
131
|
+
set: (trigger) => {
|
|
132
|
+
setValue({ ...value, tailTrim: { ...value.tailTrim, trigger } })
|
|
133
|
+
},
|
|
134
|
+
...unitBounds,
|
|
135
|
+
}]}
|
|
136
|
+
step={unitStep}
|
|
137
|
+
/>
|
|
138
|
+
<p className={css.customNote}>{t('custom.tailTrim.warning')}</p>
|
|
139
|
+
{valid ? null : <div className={css.error} role="alert">{t('custom.invalid')}</div>}
|
|
140
|
+
<div className={css.actions}>
|
|
141
|
+
<button type="button" disabled={disabled || !valid} onClick={() => { settle(save) }}>{t('custom.save')}</button>
|
|
142
|
+
<button type="button" disabled={disabled} onClick={() => { settle(reset) }}>{t('custom.reset')}</button>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface StageNumberField {
|
|
149
|
+
label: string
|
|
150
|
+
value: number
|
|
151
|
+
set: (value: number) => void
|
|
152
|
+
integer?: boolean
|
|
153
|
+
allowZero?: boolean
|
|
154
|
+
max?: number
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function StageFields({ title, enabled, disabled, onEnabled, fields, step, fieldsEnabled = enabled, t }: {
|
|
158
|
+
title: string
|
|
159
|
+
enabled: boolean
|
|
160
|
+
disabled: boolean
|
|
161
|
+
onEnabled: (enabled: boolean) => void
|
|
162
|
+
fields: readonly StageNumberField[]
|
|
163
|
+
step: number
|
|
164
|
+
fieldsEnabled?: boolean
|
|
165
|
+
t: (key: ContextCompressionLocaleKey) => string
|
|
166
|
+
}) {
|
|
167
|
+
return (
|
|
168
|
+
<fieldset className={css.stage} disabled={disabled}>
|
|
169
|
+
<legend>{title}</legend>
|
|
170
|
+
<label className={css.field}>
|
|
171
|
+
<span>{t('custom.enabled')}</span>
|
|
172
|
+
<select aria-label={title} value={enabled ? 'on' : 'off'}
|
|
173
|
+
onChange={(event) => { onEnabled(event.currentTarget.value === 'on') }}>
|
|
174
|
+
<option value="on">{t('custom.enabled.on')}</option>
|
|
175
|
+
<option value="off">{t('custom.enabled.off')}</option>
|
|
176
|
+
</select>
|
|
177
|
+
</label>
|
|
178
|
+
<div className={css.fieldGrid}>
|
|
179
|
+
{fields.map(field => (
|
|
180
|
+
<label className={css.field} key={field.label}>
|
|
181
|
+
<span>{field.label}</span>
|
|
182
|
+
<input
|
|
183
|
+
type="number"
|
|
184
|
+
value={field.value}
|
|
185
|
+
min={field.allowZero === true ? 0 : field.integer === true ? 1 : step}
|
|
186
|
+
max={field.max}
|
|
187
|
+
step={field.integer === true ? 1 : step}
|
|
188
|
+
disabled={!fieldsEnabled || disabled}
|
|
189
|
+
onChange={(event) => { field.set(Number(event.currentTarget.value)) }}
|
|
190
|
+
/>
|
|
191
|
+
</label>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
</fieldset>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Convert a legacy custom policy to V3 format. */
|
|
199
|
+
export function editableCustom(value: CustomCompressionPolicy): CustomCompressionPolicyV3 {
|
|
200
|
+
if (value.version === 3) return structuredClone(value)
|
|
201
|
+
return {
|
|
202
|
+
version: 3,
|
|
203
|
+
unit: value.unit,
|
|
204
|
+
fresh: structuredClone(value.fresh),
|
|
205
|
+
aggregate: structuredClone(value.aggregate),
|
|
206
|
+
history: {
|
|
207
|
+
enabled: value.history.enabled,
|
|
208
|
+
trigger: value.history.trigger,
|
|
209
|
+
keepRecentToolCalls: 10,
|
|
210
|
+
keepRecentTokens: value.history.keepRecent,
|
|
211
|
+
minReclaim: value.history.minReclaim,
|
|
212
|
+
},
|
|
213
|
+
prefixPolicy: value.prefixPolicy,
|
|
214
|
+
tailTrim: value.version === 1 ? { enabled: false, trigger: 700_000 } : structuredClone(value.tailTrim),
|
|
215
|
+
}
|
|
216
|
+
}
|