Skip to content

Commit 992ab2d

Browse files
committed
Display agent capabilities on handshake #97
1 parent af645af commit 992ab2d

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

agent-shell.el

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,76 @@ https://agentclientprotocol.com/protocol/schema#param-stop-reason"
786786
commands
787787
"\n")))
788788

789+
(defun agent-shell--format-agent-capabilities (capabilities)
790+
"Format agent CAPABILITIES for shell rendering.
791+
792+
CAPABILITIES is as per ACP spec:
793+
794+
https://agentclientprotocol.com/protocol/schema#agentcapabilities
795+
796+
Groups capabilities by category and displays them as comma-separated values.
797+
798+
Example output:
799+
800+
prompt image, and embedded context
801+
mcp http, and sse"
802+
(let* ((case-fold-search nil)
803+
(categories (delq nil
804+
(mapcar
805+
(lambda (pair)
806+
(let* ((key (if (symbolp (car pair))
807+
(symbol-name (car pair))
808+
(car pair)))
809+
(value (cdr pair))
810+
;; "prompt Capabilities" -> "prompt"
811+
(group-name (replace-regexp-in-string
812+
" Capabilities$" ""
813+
;; "promptCapabilities" -> "prompt Capabilities"
814+
(replace-regexp-in-string "\\([a-z]\\)\\([A-Z]\\)" "\\1 \\2" key))))
815+
(cond
816+
;; Nested capability groups (promptCapabilities, mcpCapabilities)
817+
((and (listp value)
818+
(not (vectorp value))
819+
(consp (car value)))
820+
(when-let ((enabled-items (delq nil (mapcar
821+
(lambda (cap-pair)
822+
(when (eq (cdr cap-pair) t)
823+
(let* ((cap-key (car cap-pair))
824+
(cap-name (if (symbolp cap-key)
825+
(symbol-name cap-key)
826+
cap-key)))
827+
(downcase
828+
(replace-regexp-in-string
829+
"\\([a-z]\\)\\([A-Z]\\)" "\\1 \\2"
830+
cap-name)))))
831+
value))))
832+
(cons (downcase group-name)
833+
(if (= (length enabled-items) 1)
834+
(car enabled-items)
835+
(concat (string-join (butlast enabled-items) ", ")
836+
" and "
837+
(car (last enabled-items)))))))
838+
;; Top-level boolean capabilities (loadSession)
839+
((eq value t)
840+
(cons (downcase group-name) nil)))))
841+
capabilities)))
842+
(max-name-length (seq-reduce (lambda (acc pair)
843+
(max acc (length (car pair))))
844+
categories
845+
0)))
846+
(mapconcat
847+
(lambda (pair)
848+
(let ((category (car pair))
849+
(tags (cdr pair)))
850+
(concat
851+
(propertize (format (format "%%-%ds" max-name-length) category)
852+
'font-lock-face 'font-lock-function-name-face)
853+
(when tags
854+
(concat " "
855+
(propertize tags 'font-lock-face 'font-lock-comment-face))))))
856+
categories
857+
"\n")))
858+
789859
(defun agent-shell--make-diff-info (acp-content)
790860
"Make diff information from ACP's tool_call_update's ACP-CONTENT.
791861
@@ -1587,7 +1657,13 @@ Must provide ON-INITIATED (lambda ())."
15871657
(let ((embedded-context-supported
15881658
(map-nested-elt response '(agentCapabilities promptCapabilities embeddedContext))))
15891659
(map-put! agent-shell--state :agent-supports-embedded-context
1590-
(eq embedded-context-supported t))))
1660+
(eq embedded-context-supported t)))
1661+
(when-let ((agent-capabilities (map-elt response 'agentCapabilities)))
1662+
(agent-shell--update-dialog-block
1663+
:state agent-shell--state
1664+
:block-id "agent_capabilities"
1665+
:label-left (propertize "Agent capabilities" 'font-lock-face 'font-lock-doc-markup-face)
1666+
:body (agent-shell--format-agent-capabilities agent-capabilities))))
15911667
(funcall on-initiated))
15921668
:on-failure (agent-shell--make-error-handler
15931669
:state agent-shell--state :shell shell)))

tests/agent-shell-tests.el

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,5 +443,38 @@
443443
(setq has-diff-face t)))
444444
(should has-diff-face))))
445445

446+
(ert-deftest agent-shell--format-agent-capabilities-test ()
447+
"Test `agent-shell--format-agent-capabilities' function."
448+
;; Test with multiple capabilities (includes comma)
449+
(let ((capabilities '((promptCapabilities (image . t) (audio . :false) (embeddedContext . t))
450+
(mcpCapabilities (http . t) (sse . t)))))
451+
(should (equal (substring-no-properties
452+
(agent-shell--format-agent-capabilities capabilities))
453+
(string-trim"
454+
prompt image and embedded context
455+
mcp http and sse"))))
456+
457+
;; Test with single capability per category (no comma)
458+
(let ((capabilities '((promptCapabilities (image . t))
459+
(mcpCapabilities (http . t)))))
460+
(should (equal (substring-no-properties
461+
(agent-shell--format-agent-capabilities capabilities))
462+
(string-trim "
463+
prompt image
464+
mcp http"))))
465+
466+
;; Test with top-level boolean capability (loadSession)
467+
(let ((capabilities '((loadSession . t)
468+
(promptCapabilities (image . t) (embeddedContext . t)))))
469+
(should (equal (substring-no-properties
470+
(agent-shell--format-agent-capabilities capabilities))
471+
(string-trim "
472+
load session
473+
prompt image and embedded context"))))
474+
475+
;; Test with all capabilities disabled (should return empty string)
476+
(let ((capabilities '((promptCapabilities (image . :false) (audio . :false)))))
477+
(should (equal (agent-shell--format-agent-capabilities capabilities) ""))))
478+
446479
(provide 'agent-shell-tests)
447480
;;; agent-shell-tests.el ends here

0 commit comments

Comments
 (0)