Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 56 additions & 26 deletions agent-shell-usage.el
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,17 @@ context window usage, and cost information after each agent response."
:group 'agent-shell)

(defcustom agent-shell-show-context-usage-indicator t
"Non-nil to show the context usage indicator in the header and mode line.
"Whether and how to show the context usage indicator.

The indicator displays context window usage as a vertical bar character,
color-coded from green (low) to yellow (high) to red (critical).
When set to t, displays a vertical bar character indicating
fill level. When set to `detailed', displays a numeric format
like \"➤ 29k/200k (29%%)\". When nil, no indicator is shown.

Color-coded: green (low), yellow (high), red (critical).
Only appears when the ACP server provides usage information."
:type 'boolean
:type '(choice (const :tag "Hidden" nil)
(const :tag "Bar" t)
(const :tag "Detailed" detailed))
:group 'agent-shell)

(cl-defun agent-shell--save-usage (&key state acp-usage)
Expand Down Expand Up @@ -191,36 +196,61 @@ When MULTILINE is non-nil, format as right-aligned labeled rows."
'font-lock-face 'font-lock-comment-face)
cost))))

(defun agent-shell--context-usage-face (percentage)
"Return the face for context usage at PERCENTAGE.
Green for normal, yellow for warning, red for critical."
(cond
((>= percentage 85) 'error)
((>= percentage 60) 'warning)
(t 'success)))

(defun agent-shell--context-usage-indicator-bar (usage context-used context-size)
"Return a bar indicator for context USAGE.
CONTEXT-USED and CONTEXT-SIZE are token counts."
(let* ((percentage (/ (* 100.0 context-used) context-size))
(indicator (cond
((>= percentage 100) "█")
((>= percentage 87.5) "▇")
((>= percentage 75) "▆")
((>= percentage 62.5) "▅")
((>= percentage 50) "▄")
((>= percentage 37.5) "▃")
((>= percentage 25) "▂")
((> percentage 0) "▁")
(t nil))))
(when indicator
(propertize indicator
'face (agent-shell--context-usage-face percentage)
'help-echo (agent-shell--format-usage usage)))))

(defun agent-shell--context-usage-indicator-detailed (usage context-used context-size)
"Return a detailed indicator for context USAGE.
CONTEXT-USED and CONTEXT-SIZE are token counts.
Format: \"29k/200k (29%)\"."
(let ((percentage (/ (* 100.0 context-used) context-size)))
(propertize (format "%s/%s (%.0f%%%%)"
(agent-shell--format-number-compact context-used)
(agent-shell--format-number-compact context-size)
percentage)
'face (agent-shell--context-usage-face percentage)
'help-echo (agent-shell--format-usage usage))))

(defun agent-shell--context-usage-indicator ()
"Return a single character indicating context usage percentage.
Uses Unicode vertical block characters to show fill level.
"Return a string indicating context usage percentage.
Dispatches to bar or detailed indicator based on
`agent-shell-show-context-usage-indicator'.
Only returns an indicator if enabled and usage data is available."
(when-let* ((agent-shell-show-context-usage-indicator)
((agent-shell--usage-has-data-p (map-elt (agent-shell--state) :usage)))
(usage (map-elt (agent-shell--state) :usage))
(context-used (map-elt usage :context-used))
(context-size (map-elt usage :context-size))
((> context-size 0)))
(let* ((percentage (/ (* 100.0 context-used) context-size))
;; Unicode vertical block characters from empty to full
(indicator (cond
((>= percentage 100) "█") ; Full
((>= percentage 87.5) "▇")
((>= percentage 75) "▆")
((>= percentage 62.5) "▅")
((>= percentage 50) "▄")
((>= percentage 37.5) "▃")
((>= percentage 25) "▂")
((> percentage 0) "▁")
(t nil))) ; Return nil for no usage
(face (cond
((>= percentage 85) 'error) ; Red for critical
((>= percentage 60) 'warning) ; Yellow/orange for warning
(t 'success)))) ; Green for normal
(when indicator
(propertize indicator
'face face
'help-echo (agent-shell--format-usage usage))))))
(pcase agent-shell-show-context-usage-indicator
('detailed
(agent-shell--context-usage-indicator-detailed usage context-used context-size))
(_
(agent-shell--context-usage-indicator-bar usage context-used context-size)))))

(provide 'agent-shell-usage)
;;; agent-shell-usage.el ends here
24 changes: 15 additions & 9 deletions agent-shell.el
Original file line number Diff line number Diff line change
Expand Up @@ -2909,7 +2909,8 @@ BINDINGS is a list of alists defining key bindings to display, each with:
(concat " ➤ " (map-elt header-model :session-id))
"")
(if (map-elt header-model :context-indicator)
(concat " " (map-elt header-model :context-indicator))
(concat (if (> (length (map-elt header-model :context-indicator)) 1) " ➤ " " ")
(map-elt header-model :context-indicator))
"")
(if (map-elt header-model :busy-indicator-frame)
(map-elt header-model :busy-indicator-frame)
Expand Down Expand Up @@ -3001,17 +3002,22 @@ BINDINGS is a list of alists defining key bindings to display, each with:
(dx . "8"))
(map-elt header-model :mode-name))))
(when (map-elt header-model :context-indicator)
(let* (;; Extract the face from the propertized string
(face (get-text-property 0 'face (map-elt header-model :context-indicator)))
;; Get the foreground color from the face
(color (if face
(face-attribute face :foreground nil t)
(face-attribute 'default :foreground))))
(when (> (length (map-elt header-model :context-indicator)) 1)
;; Add separator arrow
(dom-append-child text-node
(dom-node 'tspan
`((fill . ,color)
`((fill . ,(face-attribute 'default :foreground))
(dx . "8"))
(substring-no-properties (map-elt header-model :context-indicator))))))
"➤")))
;; Add context indicator
(dom-append-child text-node
(dom-node 'tspan
`((fill . ,(face-attribute
(or (get-text-property 0 'face (map-elt header-model :context-indicator))
'default)
:foreground nil t))
(dx . "8"))
(format-mode-line (map-elt header-model :context-indicator)))))
(when (map-elt header-model :busy-indicator-frame)
(dom-append-child text-node
(dom-node 'tspan
Expand Down
58 changes: 58 additions & 0 deletions tests/agent-shell-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -1924,5 +1924,63 @@ code block content
(should-not responded)
(should (equal (map-elt state :last-entry-type) "session/request_permission"))))))

;;; Tests for agent-shell-show-context-usage-indicator

(ert-deftest agent-shell--context-usage-indicator-bar-test ()
"Test `agent-shell--context-usage-indicator' bar mode."
(let ((agent-shell--state
(list (cons :buffer (current-buffer))
(cons :usage (list (cons :context-used 50000)
(cons :context-size 200000)
(cons :total-tokens 50000))))))
(cl-letf (((symbol-function 'agent-shell--state)
(lambda () agent-shell--state)))
(let ((agent-shell-show-context-usage-indicator t))
(let ((result (agent-shell--context-usage-indicator)))
(should result)
(should (= (length (substring-no-properties result)) 1))
(should (eq (get-text-property 0 'face result) 'success)))))))

(ert-deftest agent-shell--context-usage-indicator-detailed-test ()
"Test `agent-shell--context-usage-indicator' detailed mode."
(let ((agent-shell--state
(list (cons :buffer (current-buffer))
(cons :usage (list (cons :context-used 30000)
(cons :context-size 200000)
(cons :total-tokens 30000))))))
(cl-letf (((symbol-function 'agent-shell--state)
(lambda () agent-shell--state)))
(let ((agent-shell-show-context-usage-indicator 'detailed))
(let ((result (agent-shell--context-usage-indicator)))
(should result)
(should (string-match-p "30k/200k" (substring-no-properties result)))
(should (string-match-p "15%%" (substring-no-properties result)))
(should (eq (get-text-property 0 'face result) 'success)))))))

(ert-deftest agent-shell--context-usage-indicator-detailed-warning-test ()
"Test `agent-shell--context-usage-indicator' detailed mode with warning face."
(let ((agent-shell--state
(list (cons :buffer (current-buffer))
(cons :usage (list (cons :context-used 140000)
(cons :context-size 200000)
(cons :total-tokens 140000))))))
(cl-letf (((symbol-function 'agent-shell--state)
(lambda () agent-shell--state)))
(let ((agent-shell-show-context-usage-indicator 'detailed))
(let ((result (agent-shell--context-usage-indicator)))
(should (eq (get-text-property 0 'face result) 'warning)))))))

(ert-deftest agent-shell--context-usage-indicator-nil-test ()
"Test `agent-shell--context-usage-indicator' returns nil when disabled."
(let ((agent-shell--state
(list (cons :buffer (current-buffer))
(cons :usage (list (cons :context-used 50000)
(cons :context-size 200000)
(cons :total-tokens 50000))))))
(cl-letf (((symbol-function 'agent-shell--state)
(lambda () agent-shell--state)))
(let ((agent-shell-show-context-usage-indicator nil))
(should-not (agent-shell--context-usage-indicator))))))

(provide 'agent-shell-tests)
;;; agent-shell-tests.el ends here