@@ -607,6 +607,13 @@ behavior)."
607607 " Face for the free-space segment of the context-usage bar."
608608 :group 'eca )
609609
610+ (defface eca-chat-context-compaction-marker-face
611+ '((((background dark )) (:foreground " #d7dae0" ))
612+ (((background light )) (:foreground " #383a42" )))
613+ " Face for the auto-compaction threshold marker on the context bar.
614+ Its foreground is also used as the marker color in graphical frames."
615+ :group 'eca )
616+
610617(defface eca-chat-elapsed-time-face
611618 '((t :height 0.9 :inherit font-lock-comment-face ))
612619 " Face for the elapsed time indicator in mode-line of the chat."
@@ -712,6 +719,9 @@ whether the first user prompt should erase the banner first.")
712719(defvar-local eca-chat--session-tokens nil )
713720(defvar-local eca-chat--session-limit-context nil )
714721(defvar-local eca-chat--session-limit-output nil )
722+ (defvar-local eca-chat--session-auto-compact-percentage nil
723+ " Context-window percentage at which the server auto-compacts.
724+ Nil when auto-compaction is disabled or unknown." )
715725(defvar-local eca-chat--context-breakdown nil
716726 " Latest context-window usage breakdown plist for the current chat.
717727Keys: :categories (vector of :name/:tokens plists), :usedTokens,
@@ -1091,6 +1101,14 @@ Replace the job status emoji with NEW-EMOJI."
10911101 :type 'integer
10921102 :group 'eca )
10931103
1104+ (defcustom eca-chat-context-bar-show-compaction-marker t
1105+ " Whether to mark the auto-compaction threshold on the context bar."
1106+ :type 'boolean
1107+ :group 'eca )
1108+
1109+ (defconst eca-chat--context-bar-marker-px 2
1110+ " Width in pixels of the auto-compaction marker in graphical frames." )
1111+
10941112(defconst eca-chat--context-category-faces
10951113 '((" System prompt" . eca-chat-context-system-prompt-face)
10961114 (" Rules" . eca-chat-context-rules-face)
@@ -1138,11 +1156,12 @@ Prefers the server color, falling back to the client face foreground."
11381156 (face-foreground 'eca-chat-context-free-face nil t )
11391157 " #5c6370" )))
11401158
1141- (defun eca-chat--context-bar-help (breakdown used free limit )
1159+ (defun eca-chat--context-bar-help (breakdown used free limit &optional compact-pct )
11421160 " Build the help-echo legend for the context bar from BREAKDOWN.
11431161USED, FREE and LIMIT are the aggregate token counts. Each line is
11441162prefixed with a colored swatch matching the bar so the colors map to
1145- their category."
1163+ their category. COMPACT-PCT, when set, notes the auto-compaction
1164+ threshold percentage."
11461165 (let* ((nf #'eca-chat--number->friendly-number )
11471166 ; ; Prefer the server-provided emoji swatch (consistent with the
11481167 ; ; /context text); fall back to a colored block for older servers.
@@ -1168,6 +1187,11 @@ their category."
11681187 (funcall swatch (plist-get breakdown :freeEmoji )
11691188 (eca-chat--context-free-face-spec breakdown))
11701189 (funcall nf free)))
1190+ (when (and compact-pct (numberp compact-pct) (> compact-pct 0 ))
1191+ (format " \n Auto-compaction at %s%% "
1192+ (if (= compact-pct (round compact-pct))
1193+ (number-to-string (round compact-pct))
1194+ (format " %. 1f" compact-pct))))
11711195 " \n\n Click to run /context" )))
11721196
11731197(defvar eca-chat--context-bar-mode-line-map
@@ -1220,11 +1244,12 @@ number of cells available when there are more categories than cells)."
12201244 when (> n 0 ) do (setq last cat))
12211245 (eca-chat--context-category-face-spec (or last (car categories)))))
12221246
1223- (defun eca-chat--context-bar-chars (categories breakdown width frac )
1247+ (defun eca-chat--context-bar-chars (categories breakdown width frac &optional marker-frac )
12241248 " Build the block-character context bar string for terminal frames.
12251249CATEGORIES/BREAKDOWN carry the usage data, WIDTH is the cell count and
12261250FRAC the used fraction (0..1). The used/free edge uses a fractional
1227- block for sub-cell precision."
1251+ block for sub-cell precision. MARKER-FRAC, when non-nil, draws the
1252+ auto-compaction threshold marker on the matching cell."
12281253 (let* ((colored-exact (* width frac))
12291254 (colored-cells (min width (floor colored-exact)))
12301255 (edge-eighths (if (< colored-cells width)
@@ -1244,34 +1269,69 @@ block for sub-cell precision."
12441269 (when (> free-cells 0 )
12451270 (setq bar (concat bar (propertize (make-string free-cells ?█ )
12461271 'face (eca-chat--context-free-face-spec breakdown))))))
1272+ (when (and marker-frac (> (length bar) 0 ))
1273+ (let ((idx (min (1- (length bar))
1274+ (max 0 (floor (* width (min 1.0 (max 0.0 marker-frac))))))))
1275+ (setq bar (concat (substring bar 0 idx)
1276+ (propertize " │" 'face 'eca-chat-context-compaction-marker-face )
1277+ (substring bar (1+ idx))))))
12471278 bar))
12481279
1249- (defun eca-chat--context-bar-pixels (categories breakdown width frac )
1280+ (defun eca-chat--context-bar-pixels-insert-marker (segs marker-x )
1281+ " Overlay the auto-compaction marker on SEGS near MARKER-X pixels.
1282+ SEGS is a list of (PX . COLOR); return a new list with the same total
1283+ width, the marker overwriting `eca-chat--context-bar-marker-px' pixels
1284+ clamped to stay fully visible."
1285+ (let* ((total (apply #'+ 0 (mapcar #'car segs)))
1286+ (mw (min eca-chat--context-bar-marker-px total))
1287+ (m-start (max 0 (min marker-x (- total mw))))
1288+ (m-end (+ m-start mw))
1289+ (mcolor (or (face-foreground 'eca-chat-context-compaction-marker-face nil t )
1290+ " #888888" ))
1291+ (x 0 )
1292+ (out nil ))
1293+ (dolist (s segs)
1294+ (let* ((px (car s)) (color (cdr s))
1295+ (start x) (end (+ x px))
1296+ (le (max start (min end m-start)))
1297+ (me (max start (min end m-end))))
1298+ (when (> (- le start) 0 ) (push (cons (- le start) color) out))
1299+ (when (> (- me le) 0 ) (push (cons (- me le) mcolor) out))
1300+ (when (> (- end me) 0 ) (push (cons (- end me) color) out))
1301+ (setq x end)))
1302+ (nreverse out)))
1303+
1304+ (defun eca-chat--context-bar-pixels (categories breakdown width frac &optional marker-frac )
12501305 " Build the pixel-width thin-segment context bar for graphical frames.
12511306CATEGORIES/BREAKDOWN carry the usage data; each category is a
12521307`:background' -colored space whose pixel width is proportional to its
12531308share, giving sub-character granularity so small percentages still
12541309show as a thin sliver. WIDTH is the footprint in characters and FRAC
1255- the used fraction (0..1)."
1310+ the used fraction (0..1). MARKER-FRAC, when non-nil, overlays the
1311+ auto-compaction threshold marker at that fraction of the bar."
12561312 (let* ((char-px (max 1 (frame-char-width )))
12571313 (total-px (* width char-px))
12581314 (used-px (min total-px (round (* total-px frac))))
12591315 (tokens (mapcar (lambda (c ) (max 0 (or (plist-get c :tokens ) 0 ))) categories))
12601316 (alloc (eca-chat--context-bar-allocate tokens used-px))
1261- (seg (lambda (px color )
1262- (propertize " "
1263- 'display (list 'space :width (list px))
1264- 'face (list :background color))))
1265- (bar " " )
1317+ (segs nil )
12661318 (drawn 0 ))
12671319 (cl-loop for cat in categories for px in alloc
12681320 when (> px 0 )
1269- do (setq bar ( concat bar ( funcall seg px (eca-chat--context-category-color cat)))
1270- drawn (+ drawn px)))
1321+ do (push ( cons px (eca-chat--context-category-color cat)) segs )
1322+ ( setq drawn (+ drawn px)))
12711323 (let ((free-px (- total-px drawn)))
12721324 (when (> free-px 0 )
1273- (setq bar (concat bar (funcall seg free-px (eca-chat--context-free-color breakdown))))))
1274- bar))
1325+ (push (cons free-px (eca-chat--context-free-color breakdown)) segs)))
1326+ (setq segs (nreverse segs))
1327+ (when (and marker-frac segs)
1328+ (setq segs (eca-chat--context-bar-pixels-insert-marker
1329+ segs (round (* total-px (min 1.0 (max 0.0 marker-frac)))))))
1330+ (mapconcat (lambda (s )
1331+ (propertize " "
1332+ 'display (list 'space :width (list (car s)))
1333+ 'face (list :background (cdr s))))
1334+ segs " " )))
12751335
12761336(defun eca-chat--context-bar ()
12771337 " Return the propertized context-usage bar string, or nil.
@@ -1287,10 +1347,15 @@ per-category tokens are in the tooltip."
12871347 (limit (plist-get breakdown :contextLimit ))
12881348 (width (max 1 eca-chat-context-bar-width))
12891349 (frac (if (and limit (> limit 0 )) (min 1.0 (/ (float used) limit)) 1.0 ))
1290- (help (eca-chat--context-bar-help breakdown used free limit))
1350+ (compact-pct (when (and eca-chat-context-bar-show-compaction-marker
1351+ (numberp eca-chat--session-auto-compact-percentage)
1352+ (> eca-chat--session-auto-compact-percentage 0 ))
1353+ eca-chat--session-auto-compact-percentage))
1354+ (marker-frac (when compact-pct (min 1.0 (/ (float compact-pct) 100.0 ))))
1355+ (help (eca-chat--context-bar-help breakdown used free limit compact-pct))
12911356 (bar (if (display-graphic-p )
1292- (eca-chat--context-bar-pixels categories breakdown width frac)
1293- (eca-chat--context-bar-chars categories breakdown width frac))))
1357+ (eca-chat--context-bar-pixels categories breakdown width frac marker-frac )
1358+ (eca-chat--context-bar-chars categories breakdown width frac marker-frac ))))
12941359 (propertize bar
12951360 'help-echo help
12961361 'local-map eca-chat--context-bar-mode-line-map))))
@@ -3981,6 +4046,7 @@ Must be called with `eca-chat--with-current-buffer' or equivalent."
39814046 (setq-local eca-chat--session-limit-output (plist-get (plist-get content :limit ) :output ))
39824047 (setq-local eca-chat--message-cost (plist-get content :messageCost ))
39834048 (setq-local eca-chat--session-cost (plist-get content :sessionCost ))
4049+ (setq-local eca-chat--session-auto-compact-percentage (plist-get content :autoCompactPercentage ))
39844050 (setq-local eca-chat--context-breakdown (plist-get content :contextBreakdown )))
39854051 (force-mode-line-update )))
39864052 (_ nil ))
0 commit comments