feat: validate min tokens for compaction

Nicolas Leão committed Mar 26, 2026 at 16:20 UTC 140869eb9ac0226ce15401cb4e621045a8054638
6 files changed +43 -110
plugins/_chat_compaction/api/compact_chat.py
+9 -1
@@ -2,6 +2,7 @@
2 from helpers.api import ApiHandler, Input, Output, Request, Response
3 from agent import AgentContext
4 from plugins._chat_compaction.helpers.compactor import (
5 + MIN_COMPACTION_TOKENS,
6 get_compaction_stats,
7 run_compaction,
8 )
@@ -28,8 +29,15 @@ class CompactChat(ApiHandler):
29 if visible_count <= 1:
30 return Response("Not enough messages to compact", 400)
31
32 + # Gate both stats and compact — no point opening the modal for tiny chats
33 + stats = await get_compaction_stats(context)
34 + if stats["token_count"] < MIN_COMPACTION_TOKENS:
35 + return {
36 + "ok": False,
37 + "message": f"Not enough content to compact (minimum {MIN_COMPACTION_TOKENS:,} tokens)",
38 + }
39 +
40 if action == "stats":
32 - stats = await get_compaction_stats(context)
41 return {"ok": True, "stats": stats}
42
43 elif action == "compact":
plugins/_chat_compaction/helpers/compactor.py
+2
@@ -13,6 +13,8 @@ from helpers.persist_chat import (
13 remove_msg_files,
14 )
15 from helpers.state_monitor_integration import mark_dirty_all
16 +
17 +MIN_COMPACTION_TOKENS = 1000
18 from plugins._model_config.helpers.model_config import (
19 get_chat_model_config,
20 get_utility_model_config,
plugins/_chat_compaction/plugin.yaml
-4
@@ -2,7 +2,3 @@ name: _chat_compaction
2 title: Chat Compaction
3 description: Compact entire chat history into a single optimized summary message.
4 version: 1.0.0
5 -settings_sections:
6 - - agent
7 -per_project_config: false
8 -per_agent_config: false
plugins/_chat_compaction/webui/compact-modal.html
+26 -1
@@ -61,6 +61,13 @@
61 <div class="model-name" x-text="$store.compactStore.selectedModelDisplay"></div>
62 </div>
63
64 + <template x-if="!$store.compactStore?.canCompact">
65 + <div class="stats-info">
66 + <span class="material-symbols-outlined">info</span>
67 + <span>Conversation is too short to compact (minimum 1,000 tokens).</span>
68 + </div>
69 + </template>
70 +
71 <div class="stats-warning">
72 <span class="material-symbols-outlined">warning</span>
73 <span>This action cannot be undone. The original conversation will be replaced with a summary.</span>
@@ -83,7 +90,7 @@
90 <button
91 class="button primary danger"
92 @click="$store.compactStore?.compact()"
86 - :disabled="$store.compactStore?.compacting || !$store.compactStore?.stats"
93 + :disabled="$store.compactStore?.compacting || !$store.compactStore?.canCompact"
94 >
95 <template x-if="$store.compactStore?.compacting">
96 <span class="loading-spinner"></span>
@@ -265,6 +272,24 @@
272 text-overflow: ellipsis;
273 }
274
275 + .stats-info {
276 + display: flex;
277 + align-items: flex-start;
278 + gap: var(--spacing-sm);
279 + padding: var(--spacing-md);
280 + margin-bottom: var(--spacing-md);
281 + background: var(--color-info-bg, rgba(59, 130, 246, 0.1));
282 + border: 1px solid var(--color-info-border, rgba(59, 130, 246, 0.3));
283 + border-radius: var(--border-radius-md);
284 + color: var(--color-info-text, #1e40af);
285 + font-size: 0.85rem;
286 + }
287 +
288 + .stats-info .material-symbols-outlined {
289 + font-size: 1.2rem;
290 + flex-shrink: 0;
291 + }
292 +
293 .stats-warning {
294 display: flex;
295 align-items: flex-start;
plugins/_chat_compaction/webui/compact-store.js
+6
@@ -13,6 +13,12 @@ export const store = createStore("compactStore", {
13 selectedPresetName: "",
14 useChatModel: true,
15
16 + minTokens: 1000,
17 +
18 + get canCompact() {
19 + return this.stats && this.stats.token_count >= this.minTokens;
20 + },
21 +
22 get selectedModelDisplay() {
23 if (this.selectedPresetName) {
24 const preset = this.presets.find(
plugins/_chat_compaction/webui/config.html deleted
-104
@@ -1,104 +0,0 @@
1 -<html>
2 -<head>
3 - <title>Compaction Plugin Settings</title>
4 -</head>
5 -<body>
6 - <div x-data>
7 - <template x-if="config">
8 - <div>
9 - <div class="section-title">Compaction Configuration</div>
10 - <div class="section-description">
11 - Configure how the chat compaction feature works.
12 - </div>
13 -
14 - <div class="field">
15 - <div class="field-label">
16 - <div class="field-title">Use chat model</div>
17 - <div class="field-description">
18 - When enabled, uses the currently selected chat model for compaction.
19 - When disabled, uses the utility model (usually faster and cheaper).
20 - </div>
21 - </div>
22 - <div class="field-control">
23 - <label class="toggle">
24 - <input type="checkbox" x-model="config.use_chat_model" />
25 - <span class="toggler"></span>
26 - </label>
27 - </div>
28 - </div>
29 - </div>
30 - </template>
31 - </div>
32 -
33 - <style>
34 - .section-title {
35 - font-size: 1rem;
36 - font-weight: 600;
37 - margin-bottom: 8px;
38 - color: var(--color-text, #e5e5e5);
39 - }
40 - .section-description {
41 - font-size: 0.85rem;
42 - color: var(--color-text-secondary, #999);
43 - margin-bottom: 20px;
44 - line-height: 1.5;
45 - }
46 - .field {
47 - margin-bottom: 20px;
48 - }
49 - .field-label {
50 - margin-bottom: 8px;
51 - }
52 - .field-title {
53 - font-weight: 500;
54 - color: var(--color-text, #e5e5e5);
55 - margin-bottom: 4px;
56 - }
57 - .field-description {
58 - font-size: 0.8rem;
59 - color: var(--color-text-secondary, #999);
60 - line-height: 1.4;
61 - }
62 - .field-control {
63 - margin-top: 8px;
64 - }
65 - .toggle {
66 - display: inline-flex;
67 - align-items: center;
68 - cursor: pointer;
69 - }
70 - .toggle input {
71 - display: none;
72 - }
73 - .toggler {
74 - width: 44px;
75 - height: 24px;
76 - background: var(--color-border, #444);
77 - border-radius: 12px;
78 - position: relative;
79 - transition: background 0.2s;
80 - }
81 - .toggler::after {
82 - content: '';
83 - position: absolute;
84 - width: 20px;
85 - height: 20px;
86 - background: white;
87 - border-radius: 50%;
88 - top: 2px;
89 - left: 2px;
90 - transition: transform 0.2s;
91 - }
92 - .toggle input:checked + .toggler {
93 - background: var(--color-primary, #3b82f6);
94 - }
95 - .toggle input:checked + .toggler::after {
96 - transform: translateX(20px);
97 - }
98 - </style>
99 -
100 - <script type="module">
101 - import { store } from "/components/plugins/plugin-settings-store.js";
102 - </script>
103 -</body>
104 -</html>