compressed-context 0.1.0 → 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 (57) hide show
  1. package/README.md +59 -51
  2. package/package.json +64 -64
  3. package/src/compression/active-summary-cache.ts +50 -50
  4. package/src/compression/adopt.ts +57 -57
  5. package/src/compression/auto.ts +386 -372
  6. package/src/compression/babel.ts +85 -85
  7. package/src/compression/block.ts +25 -25
  8. package/src/compression/cache-history.ts +75 -75
  9. package/src/compression/compaction.ts +189 -189
  10. package/src/compression/decompress-tool.ts +74 -74
  11. package/src/compression/decompress.ts +292 -292
  12. package/src/compression/deleted-sessions.ts +24 -24
  13. package/src/compression/distill.ts +220 -220
  14. package/src/compression/events.ts +275 -275
  15. package/src/compression/markdown.ts +53 -53
  16. package/src/compression/normalize.ts +114 -114
  17. package/src/compression/render.ts +70 -70
  18. package/src/compression/schema.ts +131 -84
  19. package/src/compression/select.ts +123 -123
  20. package/src/compression/session-message.ts +112 -112
  21. package/src/compression/settings-rpc.ts +123 -111
  22. package/src/compression/settings.ts +175 -175
  23. package/src/compression/shape.ts +75 -0
  24. package/src/compression/state.ts +248 -248
  25. package/src/compression/summarize.ts +534 -595
  26. package/src/compression/summary.ts +73 -73
  27. package/src/compression/tool.ts +184 -142
  28. package/src/compression/transcript-store.ts +258 -258
  29. package/src/compression/transcript.ts +63 -63
  30. package/src/compression/validate.ts +81 -81
  31. package/src/i18n/en.ts +93 -93
  32. package/src/i18n/index.ts +28 -28
  33. package/src/i18n/types.ts +89 -89
  34. package/src/i18n/zh.ts +93 -93
  35. package/src/index.ts +116 -108
  36. package/src/shared/blocks.ts +17 -17
  37. package/src/shared/cache.ts +199 -199
  38. package/src/shared/format.ts +23 -23
  39. package/src/shared/media.ts +184 -184
  40. package/src/shared/parse.ts +25 -25
  41. package/src/shared/parts.ts +72 -72
  42. package/src/shared/rpc.ts +145 -145
  43. package/src/shared/scan.ts +32 -32
  44. package/src/shared/settings.ts +466 -471
  45. package/src/shared/stats.ts +142 -142
  46. package/src/shared/tokens.ts +259 -259
  47. package/src/tui/chart/cache-chart.ts +237 -237
  48. package/src/tui/chart/color.ts +58 -58
  49. package/src/tui/index.tsx +109 -111
  50. package/src/tui/panel/cache.tsx +203 -203
  51. package/src/tui/panel/data.ts +105 -105
  52. package/src/tui/panel/index.tsx +176 -176
  53. package/src/tui/panel/summaries.tsx +203 -192
  54. package/src/tui/settings/dialogs.tsx +215 -215
  55. package/src/tui/settings/options.ts +238 -238
  56. package/src/tui/settings.tsx +418 -418
  57. package/src/tui/types.ts +4 -4
@@ -1,215 +1,215 @@
1
- import type { Context } from "@opencode/plugin/tui/context"
2
- import { createMemo, createSignal, Show, type ParentProps } from "solid-js"
3
-
4
- import { formatTokens } from "../../shared/format.ts"
5
- import type { Messages } from "../../i18n/index.ts"
6
- import {
7
- MAX_TRIGGER_PERCENT,
8
- MIN_TRIGGER_PERCENT,
9
- parseSummaryPercent,
10
- parseTokenCount,
11
- } from "../../shared/settings.ts"
12
- import { percentLabel } from "./options.ts"
13
-
14
- /**
15
- * 输入:TUI 上下文 + 标题 + 内容。
16
- * 输出:与原生对话框一致的外框(左右 3 格内边距、标题栏、底部操作行)。
17
- * 预期:标题在左、esc 在右;内容区与底部操作行各留一空行。
18
- */
19
- function DialogFrame(
20
- props: ParentProps<{ readonly context: Context; readonly title: string; readonly submit: string }>,
21
- ) {
22
- const theme = props.context.theme
23
- return (
24
- <box paddingLeft={3} paddingRight={3} paddingBottom={1} gap={1}>
25
- <box flexDirection="row" justifyContent="space-between" gap={1}>
26
- <text fg={theme.text.base}>
27
- <b>{props.title}</b>
28
- </text>
29
- <text
30
- fg={theme.text.muted}
31
- onMouseUp={() => {
32
- props.context.ui.dialog.clear()
33
- }}
34
- >
35
- esc
36
- </text>
37
- </box>
38
- <box gap={1}>{props.children}</box>
39
- <text fg={theme.text.muted}>{props.submit} enter</text>
40
- </box>
41
- )
42
- }
43
-
44
- /**
45
- * 输入:TUI 上下文 + 文案 + 解析/预览/提示 + 确认回调。
46
- * 输出:单行输入对话框;Token/Percent/Threshold 共用。
47
- * 预期:只接受 parse 返回数字的输入;非法时显示 invalid,不写入。
48
- */
49
- function DialogInput(props: {
50
- readonly context: Context
51
- readonly copy: Messages
52
- readonly title: string
53
- readonly hint: string
54
- readonly invalid: string
55
- readonly placeholder: string
56
- readonly initial: string
57
- readonly parse: (text: string) => number | undefined
58
- readonly preview: (value: number) => string
59
- readonly extra?: string
60
- readonly onConfirm: (value: number) => void
61
- }) {
62
- const theme = props.context.theme
63
- const [text, setText] = createSignal(props.initial)
64
- const parsed = createMemo(() => props.parse(text()))
65
- let area: { plainText: string } | undefined
66
-
67
- const confirm = (): void => {
68
- const value = parsed()
69
- if (value === undefined) return
70
- props.onConfirm(value)
71
- props.context.ui.dialog.clear()
72
- }
73
-
74
- // 原生 DialogPrompt 用模态层接管回车;这里同样注册,避免 textarea 插入换行。
75
- props.context.keymap.layer(() => ({
76
- mode: "modal",
77
- priority: 1,
78
- commands: [{ bind: "enter", title: "Submit", group: "Dialog", run: confirm }],
79
- }))
80
-
81
- return (
82
- <DialogFrame context={props.context} title={props.title} submit={props.copy.settings.submit}>
83
- <textarea
84
- height={1}
85
- wrapMode="none"
86
- initialValue={props.initial}
87
- placeholder={props.placeholder}
88
- placeholderColor={theme.text.muted}
89
- textColor={theme.text.formfield.base}
90
- focusedTextColor={theme.text.formfield.base}
91
- cursorColor={theme.text.base}
92
- onContentChange={() => setText(area?.plainText ?? "")}
93
- ref={(input) => {
94
- area = input
95
- setTimeout(() => {
96
- if (!input.isDestroyed) input.focus()
97
- }, 1)
98
- }}
99
- />
100
- <Show
101
- when={parsed() !== undefined}
102
- fallback={<text fg={theme.text.feedback.error.base}>{props.invalid}</text>}
103
- >
104
- <text fg={theme.text.muted}>{props.preview(parsed() ?? 0)}</text>
105
- </Show>
106
- <Show when={props.extra !== undefined}>
107
- <text fg={theme.text.muted}>{props.extra}</text>
108
- </Show>
109
- <text fg={theme.text.muted}>{props.hint}</text>
110
- </DialogFrame>
111
- )
112
- }
113
-
114
- /** 输入:TUI 上下文 + 文案 + 标题/提示 + 初始值 + 合法范围 + 确认回调。 */
115
- export function TokenDialog(props: {
116
- readonly context: Context
117
- readonly copy: Messages
118
- readonly title: string
119
- readonly hint: string
120
- readonly invalid: string
121
- readonly placeholder: string
122
- readonly initial: string
123
- readonly min: number
124
- readonly max: number
125
- readonly allowZero?: boolean
126
- readonly onConfirm: (value: number) => void
127
- }) {
128
- return (
129
- <DialogInput
130
- context={props.context}
131
- copy={props.copy}
132
- title={props.title}
133
- hint={props.hint}
134
- invalid={props.invalid}
135
- placeholder={props.placeholder}
136
- initial={props.initial}
137
- parse={(text) => {
138
- if (props.allowZero === true && text.trim() === "0") return 0
139
- return parseTokenCount(text, props.min, props.max)
140
- }}
141
- preview={(value) =>
142
- value === 0 ? props.copy.settings.off : props.copy.settings.limitPreview(formatTokens(value))
143
- }
144
- onConfirm={props.onConfirm}
145
- />
146
- )
147
- }
148
-
149
- /** 输入:TUI 上下文 + 文案 + 初始百分比 + 窗口 + 允许范围 + 确认回调。 */
150
- export function PercentDialog(props: {
151
- readonly context: Context
152
- readonly copy: Messages
153
- readonly title: string
154
- readonly hint: string
155
- readonly invalid: string
156
- readonly initial: number
157
- readonly windowTokens: number | undefined
158
- readonly minPercent: number
159
- readonly maxPercent: number
160
- readonly onConfirm: (value: number) => void
161
- }) {
162
- return (
163
- <DialogInput
164
- context={props.context}
165
- copy={props.copy}
166
- title={props.title}
167
- hint={props.hint}
168
- invalid={props.invalid}
169
- placeholder="10%"
170
- initial={`${props.initial.toString()}%`}
171
- parse={(text) => {
172
- const value = parseSummaryPercent(text, props.windowTokens)
173
- if (value === undefined || value < props.minPercent || value > props.maxPercent) {
174
- return undefined
175
- }
176
- return value
177
- }}
178
- preview={(value) => percentLabel(value, props.windowTokens)}
179
- extra={
180
- props.windowTokens === undefined
181
- ? props.copy.settings.windowUnknown
182
- : props.copy.settings.windowKnown(formatTokens(props.windowTokens))
183
- }
184
- onConfirm={props.onConfirm}
185
- />
186
- )
187
- }
188
-
189
- /** 输入:TUI 上下文 + 文案 + 初始值 + 确认回调。 */
190
- export function ThresholdDialog(props: {
191
- readonly context: Context
192
- readonly copy: Messages
193
- readonly initial: string
194
- readonly onConfirm: (value: number) => void
195
- }) {
196
- return (
197
- <DialogInput
198
- context={props.context}
199
- copy={props.copy}
200
- title={props.copy.settings.threshold}
201
- hint={props.copy.settings.thresholdHint}
202
- invalid={props.copy.settings.thresholdInvalid}
203
- placeholder="80"
204
- initial={props.initial}
205
- parse={(text) => {
206
- if (!/^\d{1,2}$/.test(text)) return undefined
207
- const value = Number(text)
208
- if (value < MIN_TRIGGER_PERCENT || value > MAX_TRIGGER_PERCENT) return undefined
209
- return value
210
- }}
211
- preview={(value) => props.copy.settings.thresholdPreview(value.toString())}
212
- onConfirm={props.onConfirm}
213
- />
214
- )
215
- }
1
+ import type { Context } from "@opencode/plugin/tui/context"
2
+ import { createMemo, createSignal, Show, type ParentProps } from "solid-js"
3
+
4
+ import { formatTokens } from "../../shared/format.ts"
5
+ import type { Messages } from "../../i18n/index.ts"
6
+ import {
7
+ MAX_TRIGGER_PERCENT,
8
+ MIN_TRIGGER_PERCENT,
9
+ parseSummaryPercent,
10
+ parseTokenCount,
11
+ } from "../../shared/settings.ts"
12
+ import { percentLabel } from "./options.ts"
13
+
14
+ /**
15
+ * 输入:TUI 上下文 + 标题 + 内容。
16
+ * 输出:与原生对话框一致的外框(左右 3 格内边距、标题栏、底部操作行)。
17
+ * 预期:标题在左、esc 在右;内容区与底部操作行各留一空行。
18
+ */
19
+ function DialogFrame(
20
+ props: ParentProps<{ readonly context: Context; readonly title: string; readonly submit: string }>,
21
+ ) {
22
+ const theme = props.context.theme
23
+ return (
24
+ <box paddingLeft={3} paddingRight={3} paddingBottom={1} gap={1}>
25
+ <box flexDirection="row" justifyContent="space-between" gap={1}>
26
+ <text fg={theme.text.base}>
27
+ <b>{props.title}</b>
28
+ </text>
29
+ <text
30
+ fg={theme.text.muted}
31
+ onMouseUp={() => {
32
+ props.context.ui.dialog.clear()
33
+ }}
34
+ >
35
+ esc
36
+ </text>
37
+ </box>
38
+ <box gap={1}>{props.children}</box>
39
+ <text fg={theme.text.muted}>{props.submit} enter</text>
40
+ </box>
41
+ )
42
+ }
43
+
44
+ /**
45
+ * 输入:TUI 上下文 + 文案 + 解析/预览/提示 + 确认回调。
46
+ * 输出:单行输入对话框;Token/Percent/Threshold 共用。
47
+ * 预期:只接受 parse 返回数字的输入;非法时显示 invalid,不写入。
48
+ */
49
+ function DialogInput(props: {
50
+ readonly context: Context
51
+ readonly copy: Messages
52
+ readonly title: string
53
+ readonly hint: string
54
+ readonly invalid: string
55
+ readonly placeholder: string
56
+ readonly initial: string
57
+ readonly parse: (text: string) => number | undefined
58
+ readonly preview: (value: number) => string
59
+ readonly extra?: string
60
+ readonly onConfirm: (value: number) => void
61
+ }) {
62
+ const theme = props.context.theme
63
+ const [text, setText] = createSignal(props.initial)
64
+ const parsed = createMemo(() => props.parse(text()))
65
+ let area: { plainText: string } | undefined
66
+
67
+ const confirm = (): void => {
68
+ const value = parsed()
69
+ if (value === undefined) return
70
+ props.onConfirm(value)
71
+ props.context.ui.dialog.clear()
72
+ }
73
+
74
+ // 原生 DialogPrompt 用模态层接管回车;这里同样注册,避免 textarea 插入换行。
75
+ props.context.keymap.layer(() => ({
76
+ mode: "modal",
77
+ priority: 1,
78
+ commands: [{ bind: "enter", title: "Submit", group: "Dialog", run: confirm }],
79
+ }))
80
+
81
+ return (
82
+ <DialogFrame context={props.context} title={props.title} submit={props.copy.settings.submit}>
83
+ <textarea
84
+ height={1}
85
+ wrapMode="none"
86
+ initialValue={props.initial}
87
+ placeholder={props.placeholder}
88
+ placeholderColor={theme.text.muted}
89
+ textColor={theme.text.formfield.base}
90
+ focusedTextColor={theme.text.formfield.base}
91
+ cursorColor={theme.text.base}
92
+ onContentChange={() => setText(area?.plainText ?? "")}
93
+ ref={(input) => {
94
+ area = input
95
+ setTimeout(() => {
96
+ if (!input.isDestroyed) input.focus()
97
+ }, 1)
98
+ }}
99
+ />
100
+ <Show
101
+ when={parsed() !== undefined}
102
+ fallback={<text fg={theme.text.feedback.error.base}>{props.invalid}</text>}
103
+ >
104
+ <text fg={theme.text.muted}>{props.preview(parsed() ?? 0)}</text>
105
+ </Show>
106
+ <Show when={props.extra !== undefined}>
107
+ <text fg={theme.text.muted}>{props.extra}</text>
108
+ </Show>
109
+ <text fg={theme.text.muted}>{props.hint}</text>
110
+ </DialogFrame>
111
+ )
112
+ }
113
+
114
+ /** 输入:TUI 上下文 + 文案 + 标题/提示 + 初始值 + 合法范围 + 确认回调。 */
115
+ export function TokenDialog(props: {
116
+ readonly context: Context
117
+ readonly copy: Messages
118
+ readonly title: string
119
+ readonly hint: string
120
+ readonly invalid: string
121
+ readonly placeholder: string
122
+ readonly initial: string
123
+ readonly min: number
124
+ readonly max: number
125
+ readonly allowZero?: boolean
126
+ readonly onConfirm: (value: number) => void
127
+ }) {
128
+ return (
129
+ <DialogInput
130
+ context={props.context}
131
+ copy={props.copy}
132
+ title={props.title}
133
+ hint={props.hint}
134
+ invalid={props.invalid}
135
+ placeholder={props.placeholder}
136
+ initial={props.initial}
137
+ parse={(text) => {
138
+ if (props.allowZero === true && text.trim() === "0") return 0
139
+ return parseTokenCount(text, props.min, props.max)
140
+ }}
141
+ preview={(value) =>
142
+ value === 0 ? props.copy.settings.off : props.copy.settings.limitPreview(formatTokens(value))
143
+ }
144
+ onConfirm={props.onConfirm}
145
+ />
146
+ )
147
+ }
148
+
149
+ /** 输入:TUI 上下文 + 文案 + 初始百分比 + 窗口 + 允许范围 + 确认回调。 */
150
+ export function PercentDialog(props: {
151
+ readonly context: Context
152
+ readonly copy: Messages
153
+ readonly title: string
154
+ readonly hint: string
155
+ readonly invalid: string
156
+ readonly initial: number
157
+ readonly windowTokens: number | undefined
158
+ readonly minPercent: number
159
+ readonly maxPercent: number
160
+ readonly onConfirm: (value: number) => void
161
+ }) {
162
+ return (
163
+ <DialogInput
164
+ context={props.context}
165
+ copy={props.copy}
166
+ title={props.title}
167
+ hint={props.hint}
168
+ invalid={props.invalid}
169
+ placeholder="10%"
170
+ initial={`${props.initial.toString()}%`}
171
+ parse={(text) => {
172
+ const value = parseSummaryPercent(text, props.windowTokens)
173
+ if (value === undefined || value < props.minPercent || value > props.maxPercent) {
174
+ return undefined
175
+ }
176
+ return value
177
+ }}
178
+ preview={(value) => percentLabel(value, props.windowTokens)}
179
+ extra={
180
+ props.windowTokens === undefined
181
+ ? props.copy.settings.windowUnknown
182
+ : props.copy.settings.windowKnown(formatTokens(props.windowTokens))
183
+ }
184
+ onConfirm={props.onConfirm}
185
+ />
186
+ )
187
+ }
188
+
189
+ /** 输入:TUI 上下文 + 文案 + 初始值 + 确认回调。 */
190
+ export function ThresholdDialog(props: {
191
+ readonly context: Context
192
+ readonly copy: Messages
193
+ readonly initial: string
194
+ readonly onConfirm: (value: number) => void
195
+ }) {
196
+ return (
197
+ <DialogInput
198
+ context={props.context}
199
+ copy={props.copy}
200
+ title={props.copy.settings.threshold}
201
+ hint={props.copy.settings.thresholdHint}
202
+ invalid={props.copy.settings.thresholdInvalid}
203
+ placeholder="80"
204
+ initial={props.initial}
205
+ parse={(text) => {
206
+ if (!/^\d{1,2}$/.test(text)) return undefined
207
+ const value = Number(text)
208
+ if (value < MIN_TRIGGER_PERCENT || value > MAX_TRIGGER_PERCENT) return undefined
209
+ return value
210
+ }}
211
+ preview={(value) => props.copy.settings.thresholdPreview(value.toString())}
212
+ onConfirm={props.onConfirm}
213
+ />
214
+ )
215
+ }