Function: eca-chat--render-content

eca-chat--render-content is a natively compiled function defined in eca-chat.el.

Signature

(eca-chat--render-content SESSION CHAT-BUFFER ROLE CONTENT ROOTS &optional PARENT-TOOL-CALL-ID CHAT-ID)

Documentation

Render CONTENT inside CHAT-BUFFER for SESSION.

ROLE is the message role. ROOTS is the list of workspace roots. When PARENT-TOOL-CALL-ID is non-nil, renders as nested content inside that expandable block (subagent mode). CHAT-ID is the chat session the content belongs to, used for tool call approval requests. Falls back to the buffer-local eca-chat--id. Must be called with eca-chat--with-current-buffer or equivalent.

Source Code

;; Defined in /nix/store/jklqzw6pzz6j4jrbv8wy0iwgi7jlrbld-emacs-packages-deps/share/emacs/site-lisp/elpa/eca-20260928.1827/eca-chat.el
(defun eca-chat--render-content (session chat-buffer role content roots &optional parent-tool-call-id chat-id)
  "Render CONTENT inside CHAT-BUFFER for SESSION.
ROLE is the message role.  ROOTS is the list of workspace roots.
When PARENT-TOOL-CALL-ID is non-nil, renders as nested content inside
that expandable block (subagent mode).
CHAT-ID is the chat session the content belongs to, used for tool call
approval requests.  Falls back to the buffer-local `eca-chat--id'.
Must be called with `eca-chat--with-current-buffer' or equivalent."
  (let* ((content-id (plist-get content :contentId))
         (content-type (plist-get content :type))
         (tool-call-next-line-spacing (make-string (1+ (length eca-chat-expandable-block-open-symbol)) ?\s))
         ;; Whether the user acted on this approval from within its
         ;; block; checked now, before rendering drops the buttons.
         (approval-acted-on? (and (member content-type '("toolCallRunning" "toolCalled"
                                                          "toolCallRejected"))
                                  (eca-chat--approval-acted-on-p (plist-get content :id)))))
    ;; Tool call lifecycle events are the only content that adds or
    ;; removes approval buttons; drop the cached scan result so the
    ;; next status check rescans the buffer.
    (when (member content-type '("toolCallRun" "toolCallRunning"
                                 "toolCalled" "toolCallRejected"))
      (eca-chat--invalidate-pending-approvals-cache)
      (eca-chat--invalidate-tab-line-cache session))
    (when (and (not parent-tool-call-id)
               (or (not (equal content-type "text"))
                   (member role '("user" "system"))))
      (eca-chat--stream-flush))
    (pcase content-type
      ("metadata"
       (unless parent-tool-call-id
         (setq-local eca-chat--title (plist-get content :title))
         (eca-chat--invalidate-tab-line-cache session)))
      ("text"
       (when-let* ((text (plist-get content :text)))
         (pcase role
           ("user"
            (unless parent-tool-call-id
              ;; Capture the insertion point before adding the user
              ;; expandable so we can scope `font-lock-ensure' to just
              ;; the newly-inserted region instead of the whole buffer.
              (let ((user-msg-start (eca-chat--content-insertion-point)))
                (when eca-chat--steered-prompt
                  (setq-local eca-chat--steered-prompt nil)
                  (eca-chat--update-steer-area))
                (eca-chat--ensure-empty-line-before user-msg-start)
                (eca-chat--add-expandable-content
                 content-id
                 (propertize (string-trim text) 'font-lock-face 'eca-chat-user-messages-face)
                 (eca-buttonize
                  eca-chat-mode-map
                  (propertize "Rollback chat to before this message" 'font-lock-face 'eca-chat-rollback-face)
                  (lambda () (eca-chat--rollback session content-id (string-trim text)))))
                (when-let* ((ov (eca-chat--get-expandable-content content-id)))
                  (overlay-put ov 'eca-chat--user-message-id content-id)
                  (overlay-put ov 'eca-chat--timestamp (float-time))
                  (eca-chat--paint-user-message ov)
                  (eca-chat--linkify-user-message session ov roots))
                (setq-local eca-chat--last-response-copy-start nil)
                (setq-local eca-chat--last-response-copy-kind nil)
                (eca-chat--mark-header)
                (font-lock-ensure user-msg-start (point-max))
                ;; The user was at the prompt to send, so keep it
                ;; visible even when the message is long: the guard in
                ;; `eca-chat--ensure-prompt-visible' cannot pass then,
                ;; as the insertion pushes the prompt below the window
                ;; end.  Skip when prepending older history pages.
                (unless eca-chat--insertion-point-override
                  (eca-chat--ensure-prompt-visible t)))))
           ("system"
            (eca-chat--add-text-content
             (propertize text
                         'font-lock-face 'eca-chat-system-messages-face
                         'line-height 20)))
           (_
            (if parent-tool-call-id
                ;; Subagent: append assistant text to the parent tool call content
                (eca-chat--update-expandable-content
                 parent-tool-call-id nil text t)
              (unless (eq eca-chat--last-response-copy-kind 'text)
                (setq-local eca-chat--last-response-copy-start
                            (eca-chat--content-insertion-point)))
              (setq-local eca-chat--last-response-copy-kind 'text)
              (if (eca-chat--stream-buffering-enabled-p)
                  (eca-chat--stream-buffered-text text)
                (eca-chat--add-text-content text)
                ;; Defer fontification: let jit-lock handle visible-area
                ;; updates and run a single final ensure in the
                ;; "finished" progress arm below.
                (eca-chat--schedule-fontify)))))))
      ("url"
       (unless parent-tool-call-id
         (eca-chat--add-header
          (concat "🌐 "
                  (eca-buttonize
                   eca-chat-mode-map
                   (plist-get content :title)
                   (lambda () (browse-url (plist-get content :url))))
                  "\n\n"))))
      ("image"
       (eca-chat--render-image-content content parent-tool-call-id))
      ("flag"
       (let* ((flag-text (plist-get content :text))
              (flag-content-id (plist-get content :contentId))
              (flag-str (propertize (concat "đŸšŠī¸ī¸ " flag-text)
                                    'font-lock-face 'eca-chat-flag-face))
              (fork-btn (eca-buttonize
                         eca-chat-mode-map
                         (propertize "Fork from here" 'font-lock-face 'eca-chat-rollback-face)
                         (lambda () (eca-chat--fork-from-flag session flag-content-id))))
              (remove-btn (eca-buttonize
                           eca-chat-mode-map
                           (propertize "Remove flag" 'font-lock-face 'eca-chat-rollback-face)
                           (lambda () (eca-chat--remove-flag session flag-content-id))))
              (actions (concat fork-btn "\n" remove-btn)))
         (eca-chat--add-expandable-content flag-content-id flag-str actions)
         (when-let* ((ov (eca-chat--get-expandable-content flag-content-id)))
           (overlay-put ov 'eca-chat--flag-text flag-text)
           (overlay-put ov 'eca-chat--timestamp (float-time)))))
      ("reasonStarted"
       (let ((id (plist-get content :id))
             (label (propertize "Thinking..." 'font-lock-face 'eca-chat-reason-label-face)))
         (eca-chat--add-expandable-content id label "" parent-tool-call-id)))
      ("reasonText"
       (let ((id (plist-get content :id))
             (label (propertize "Thinking..." 'font-lock-face 'eca-chat-reason-label-face))
             (text (plist-get content :text)))
         (eca-chat--update-expandable-content id label text t parent-tool-call-id)))
      ("reasonFinished"
       (let* ((id (plist-get content :id))
              (base (propertize "Thought" 'font-lock-face 'eca-chat-reason-label-face))
              (time (when-let ((ms (plist-get content :totalTimeMs)))
                      (concat " " (eca-chat--time->presentable-time ms))))
              (label (concat base time)))
         (eca-chat--update-expandable-content id label "" t parent-tool-call-id)))
      ("hookActionStarted"
       (let* ((id (plist-get content :id))
              (name (plist-get content :name))
              (label (propertize (format "Running hook '%s'..." name) 'font-lock-face 'eca-chat-hook-label-face)))
         (eca-chat--add-expandable-content id label "" parent-tool-call-id)))
      ("hookActionFinished"
       (let* ((id (plist-get content :id))
              (name (plist-get content :name))
              (status (number-to-string (plist-get content :status)))
              (output (plist-get content :output))
              (error (plist-get content :error))
              (label (propertize (format "Executed hook '%s'" name) 'font-lock-face 'eca-chat-hook-label-face)))
         (eca-chat--update-expandable-content id label (eca-chat--content-table
                                                        (append
                                                         `(("Name" . ,name)
                                                           ("Status" . ,status))
                                                         (when output `(("Output" . ,output)))
                                                         (when error `(("Error" . ,error)))))
                                              nil parent-tool-call-id)))
      ("toolCallPrepare"
       (if (eca-chat--task-tool-call-p content)
           (unless (eca-chat--get-expandable-content eca-chat--task-block-id)
             (eca-chat--render-task-widget t))
         (when-let* ((id (plist-get content :id))
                     (name (plist-get content :name))
                     (server (plist-get content :server)))
           (let* ((argsText (plist-get content :argumentsText))
                  (details (plist-get content :details))
                  (subagent? (string= "subagent" (plist-get details :type)))
                  (label (or (plist-get content :summary)
                             (format "Preparing tool: %s__%s" server name)))
                  (current-count (gethash id eca-chat--tool-call-prepare-counters 0))
                  (cached-content (gethash id eca-chat--tool-call-prepare-content-cache ""))
                  (new-content (concat cached-content argsText))
                  (should-update-ui-p
                   (pcase eca-chat-tool-call-prepare-throttle
                     ('all t)
                     ('smart (or (= current-count 0)
                                 (= (mod current-count eca-chat-tool-call-prepare-update-interval) 0))))))
             ;; Always cache the metadata and content
             (puthash id (1+ current-count) eca-chat--tool-call-prepare-counters)
             (puthash id new-content eca-chat--tool-call-prepare-content-cache)
             ;; Only update UI when throttling permits
             (when should-update-ui-p
               (let* ((label-face (if subagent?
                                      'eca-chat-subagent-tool-call-label-face
                                    'eca-chat-mcp-tool-call-label-face))
                      (label (concat (propertize label 'font-lock-face label-face)
                                     " " eca-chat-mcp-tool-call-loading-symbol))
                      ;; Tag body and accumulated content with `eca-no-fontify' so
                      ;; the custom `font-lock-fontify-region-function' skips them
                      ;; while the tool args are still streaming (#234).  The
                      ;; property travels through `eca-chat--insert' and
                      ;; `eca-chat--update-expandable-content' to every code
                      ;; path that lands the body in the buffer; the `toolCalled'
                      ;; arm later reinserts un-tagged final content, so jit-lock
                      ;; refontifies normally on the next redisplay.
                      (body (if subagent?
                                (eca-chat--content-table `())
                              (propertize
                               (eca-chat--content-table
                                `(("Tool" . ,name)
                                  ("Server" . ,server)
                                  ("Arguments" . ,new-content)))
                               'eca-no-fontify t)))
                      (update-content (if subagent?
                                          body
                                        (propertize new-content 'eca-no-fontify t))))
                 (if (eca-chat--get-expandable-content id)
                     ;; Update with accumulated content, not just this chunk
                     (eca-chat--update-expandable-content
                      id label update-content nil parent-tool-call-id)
                   (eca-chat--add-expandable-content
                    id label body parent-tool-call-id))))))))
      ("toolCallRun"
       (when (and (eca-chat--task-tool-call-p content)
                  (plist-get content :manualApproval))
         (eca-chat--show-task-tool-call-approval session content chat-id tool-call-next-line-spacing))
       (unless (or (eca-chat--task-tool-call-p content)
                   ;; A stale run from an older history page must not
                   ;; resurrect an already-answered approval prompt (#307).
                   (and (plist-get content :manualApproval)
                        (eca-chat--tool-call-approval-resolved-p
                         (plist-get content :id))))
         (let* ((id (plist-get content :id))
                (args (plist-get content :arguments))
                (name (plist-get content :name))
                (server (plist-get content :server))
                (label (or (plist-get content :summary)
                           (format "Calling tool: %s__%s" server name)))
                (manual? (plist-get content :manualApproval))
                (status (if manual?
                            eca-chat-mcp-tool-call-pending-approval-symbol
                          eca-chat-mcp-tool-call-loading-symbol))
                (details (plist-get content :details))
                (approval-text (when manual?
                                 (eca-chat--build-tool-call-approval-str-content session id tool-call-next-line-spacing chat-id details)))
                ;; Decide before rendering: the approval buttons and the
                ;; expansion below grow the block, which can push the
                ;; prompt out of the window and fail the check afterwards.
                (viewing-bottom? (and manual?
                                      eca-chat-expand-pending-approval-tools
                                      (-some-> (get-buffer-window (current-buffer))
                                        (eca-chat--viewing-bottom-p)))))
           ;; Register subagent mapping only for top-level tool calls
           (when (and (not parent-tool-call-id)
                      (string= "subagent" (plist-get details :type)))
             (puthash (plist-get details :subagentChatId) id eca-chat--subagent-chat-id->tool-call-id))
           (pcase (plist-get details :type)
             ("fileChange" (eca-chat--tool-call-file-change-details content label approval-text nil status tool-call-next-line-spacing roots parent-tool-call-id))
             ("shellCommand" (eca-chat--tool-call-shell-command-details content label approval-text nil status parent-tool-call-id nil (not manual?)))
             ("subagent" (eca-chat--tool-call-subagent-details id args label approval-text nil status parent-tool-call-id details))
             (_ (eca-chat--update-expandable-content
                 id
                 (concat (propertize label 'font-lock-face 'eca-chat-mcp-tool-call-label-face)
                         " " status
                         "\n"
                         approval-text)
                 (eca-chat--content-table
                  `(("Tool" . ,name)
                    ("Server" . ,server)
                    ("Arguments" . ,args)))
                 nil
                 parent-tool-call-id)))
           ;; Mark this ID as having received toolCallRun so that any late-arriving
           ;; toolCallPrepare events (still in-flight for long files) don't overwrite
           ;; the approval prompt we just rendered.  Set this AFTER the pcase dispatch
           ;; so that if rendering errors, the flag doesn't poison subsequent prepare
           ;; events (which would prevent the block from ever being created).
           (when (and eca-chat-expand-pending-approval-tools manual?)
             (when parent-tool-call-id
               (eca-chat--expandable-content-toggle parent-tool-call-id t nil))
             (eca-chat--expandable-content-toggle id t nil)
             (when viewing-bottom?
               (eca-chat--ensure-tool-call-approval-visible id)))
           ;; Update parent subagent status to show pending approval
           (when (and manual? parent-tool-call-id)
             (eca-chat--update-parent-subagent-status
              parent-tool-call-id eca-chat-mcp-tool-call-pending-approval-symbol)))))
      ("toolCallRunning"
       ;; Approved (possibly from another client): drop the prompt, the
       ;; task list follows with toolCalled.
       (when (and (eca-chat--task-tool-call-p content)
                  (equal (plist-get content :id) (eca-chat--task-widget-approval-id)))
         (eca-chat--render-task-widget t))
       (unless (eca-chat--task-tool-call-p content)
         (let* ((id (plist-get content :id))
                (args (plist-get content :arguments))
                (name (plist-get content :name))
                (server (plist-get content :server))
                (label (or (plist-get content :summary)
                           (format "Running tool: %s__%s" server name)))
                (details (plist-get content :details))
                (status eca-chat-mcp-tool-call-loading-symbol)
                (elapsed-time (progn
                                (eca-chat--tool-call-elapsed-start id)
                                (eca-chat--elapsed-time-string
                                 (gethash id eca-chat--tool-call-elapsed-times)))))
           ;; Register subagent mapping only for top-level tool calls
           (when (and (not parent-tool-call-id)
                      (string= "subagent" (plist-get details :type)))
             (let ((subagent-chat-id (plist-get details :subagentChatId)))
               (unless (gethash subagent-chat-id eca-chat--subagent-chat-id->tool-call-id)
                 (puthash subagent-chat-id id eca-chat--subagent-chat-id->tool-call-id))))
           (pcase (plist-get details :type)
             ("fileChange" (eca-chat--tool-call-file-change-details content label nil elapsed-time status tool-call-next-line-spacing roots parent-tool-call-id))
             ("shellCommand" (eca-chat--tool-call-shell-command-details content label nil elapsed-time status parent-tool-call-id nil t))
             ("subagent" (eca-chat--tool-call-subagent-details id args label nil elapsed-time status parent-tool-call-id details))
             (_ (eca-chat--update-expandable-content
                 id
                 (concat (propertize label 'font-lock-face 'eca-chat-mcp-tool-call-label-face)
                         " " status elapsed-time)
                 (eca-chat--content-table
                  `(("Tool" . ,name)
                    ("Server" . ,server)
                    ("Arguments" . ,args)))
                 nil
                 parent-tool-call-id)))
           (eca-chat--mark-tool-call-approval-resolved id)
           ;; Keep parent pending while sibling approvals remain pending
           (eca-chat--restore-parent-subagent-status parent-tool-call-id))))
      ("toolCalled"
       (if (eca-chat--task-tool-call-p content)
           (eca-chat--update-task-state content)
         (let* ((id (plist-get content :id))
                (name (plist-get content :name))
                (server (plist-get content :server))
                (label (or (plist-get content :summary)
                           (format "Called tool: %s__%s" server name)))
                (args (plist-get content :arguments))
                (outputs (plist-get content :outputs))
                (output-text (if outputs
                                 (mapconcat (lambda (o) (or (plist-get o :text) "")) outputs "\n")
                               ""))
                (details (plist-get content :details))
                (time (when-let ((ms (plist-get content :totalTimeMs)))
                        (concat " " (eca-chat--time->presentable-time ms))))
                (bg? (plist-get details :background))
                (status (cond
                         ((plist-get content :error)
                          eca-chat-mcp-tool-call-error-symbol)
                         (bg? "🟡")
                         (t eca-chat-mcp-tool-call-success-symbol))))
           ;; Cleanup counters for this tool-call id to avoid unbounded growth
           (remhash id eca-chat--tool-call-prepare-counters)
           (remhash id eca-chat--tool-call-prepare-content-cache)
           ;; Stop elapsed-time tracking for this tool call
           (eca-chat--tool-call-elapsed-stop id)
           ;; Another client may have answered this ask_user question first,
           ;; resolving the tool call here; drop our now-stale prompt state.
           (eca-chat--dismiss-pending-question-for-tool-call id)
           ;; Cleanup subagent mapping only for top-level tool calls
           (when (and (not parent-tool-call-id)
                      (string= "subagent" (plist-get details :type)))
             (remhash (plist-get details :subagentChatId) eca-chat--subagent-chat-id->tool-call-id))
           (pcase (plist-get details :type)
             ("fileChange" (eca-chat--tool-call-file-change-details content label nil time status tool-call-next-line-spacing roots parent-tool-call-id))
             ("shellCommand" (eca-chat--tool-call-shell-command-details content label nil time status parent-tool-call-id output-text t))
             ("jsonOutputs" (eca-chat--tool-call-json-outputs-details content time status parent-tool-call-id))
             ("subagent" (eca-chat--tool-call-subagent-details id args label nil time status parent-tool-call-id details output-text))
             (_ (eca-chat--update-expandable-content
                 id
                 (concat (propertize label 'font-lock-face 'eca-chat-mcp-tool-call-label-face)
                         " " status time)
                 (eca-chat--content-table
                  `(("Tool"   . ,name)
                    ("Server" . ,server)
                    ("Arguments" . ,args)
                    ("Output" . ,output-text)))
                 nil
                 parent-tool-call-id)))
           (when eca-chat-shrink-called-tools
             (eca-chat--expandable-content-toggle id t t)
             (eca-chat--ensure-prompt-visible))
           (eca-chat--mark-tool-call-approval-resolved id)
           ;; Keep parent pending while sibling approvals remain pending
           (eca-chat--restore-parent-subagent-status parent-tool-call-id))))
      ("toolCallRejected"
       (when (eca-chat--task-tool-call-p content)
         (eca-chat--update-task-state content))
       (unless (eca-chat--task-tool-call-p content)
         (let* ((name (plist-get content :name))
                (server (plist-get content :server))
                (label (or (plist-get content :summary)
                           (format "Rejected tool: %s__%s" server name)))
                (args (plist-get content :arguments))
                (details (plist-get content :details))
                (status eca-chat-mcp-tool-call-error-symbol)
                (id (plist-get content :id)))
           ;; Cleanup counters for this tool-call id
           (remhash id eca-chat--tool-call-prepare-counters)
           (remhash id eca-chat--tool-call-prepare-content-cache)
           ;; Another client may have cancelled this ask_user question first,
           ;; rejecting the tool call here; drop our now-stale prompt state.
           (eca-chat--dismiss-pending-question-for-tool-call id)
           ;; Cleanup subagent mapping only for top-level tool calls
           (when (and (not parent-tool-call-id)
                      (string= "subagent" (plist-get details :type)))
             (remhash (plist-get details :subagentChatId) eca-chat--subagent-chat-id->tool-call-id))
           (pcase (plist-get details :type)
             ("fileChange" (eca-chat--tool-call-file-change-details content label nil nil status tool-call-next-line-spacing roots parent-tool-call-id))
             ("shellCommand" (eca-chat--tool-call-shell-command-details content label nil nil status parent-tool-call-id))
             ("subagent" (eca-chat--tool-call-subagent-details id args label nil nil status parent-tool-call-id details))
             (_ (eca-chat--update-expandable-content
                 id
                 (concat (propertize label
                                     'font-lock-face 'eca-chat-mcp-tool-call-label-face)
                         " "
                         eca-chat-mcp-tool-call-error-symbol)
                 (eca-chat--content-table `(("Tool" . ,name)
                                            ("Server" . ,server)
                                            ("Arguments" . ,args)))
                 nil
                 parent-tool-call-id)))
           (eca-chat--mark-tool-call-approval-resolved id)
           ;; Keep parent pending while sibling approvals remain pending
           (eca-chat--restore-parent-subagent-status parent-tool-call-id))))
      ("progress"
       (unless parent-tool-call-id
         (pcase (plist-get content :state)
           ("running"
            (setq-local eca-chat--progress-text (plist-get content :text))
            (unless eca-chat--spinner-timer
              (eca-chat--spinner-start
               (lambda ()
                 (eca-chat--refresh-progress chat-buffer))))
            (eca-chat--refresh-progress chat-buffer))
           ("finished"
            (pcase eca-chat--chat-loading
              ('stopping
               ;; Stopped prompt confirmed — minimal cleanup, no trailing newline
               (setq-local eca-chat--progress-text "")
               (eca-chat--spinner-stop)
               (eca-chat--tool-call-elapsed-stop-all)
               (when (timerp eca-chat--fontify-timer)
                 (cancel-timer eca-chat--fontify-timer)
                 (setq eca-chat--fontify-timer nil))
               ;; Region-scoped fontify: only the current turn needs
               ;; (re)fontification at end-of-stream; full-buffer
               ;; fontify is O(buffer size) and was the dominant
               ;; cost on long chats.
               (eca-chat--font-lock-ensure
                (or eca-chat--last-user-message-pos (point-min))
                (point-max))
               (eca-chat--refresh-copy-scopes)
               (eca-chat--set-chat-loading session nil)
               (eca-chat--refresh-progress chat-buffer)
               (eca-chat--send-steered-prompt session)
               (eca-chat--send-queued-prompt session)
               (run-hooks 'eca-chat-finished-hook))
              ('t
               ;; Normal completion
               (setq-local eca-chat--progress-text "")
               (eca-chat--spinner-stop)
               (eca-chat--tool-call-elapsed-stop-all)
               (eca-chat--add-text-content "\n")
               (when (timerp eca-chat--fontify-timer)
                 (cancel-timer eca-chat--fontify-timer)
                 (setq eca-chat--fontify-timer nil))
               ;; Final guaranteed fontify before table alignment so
               ;; the beautifier sees fully-fontified text.  Scoped
               ;; to the current turn so cost does not grow with
               ;; chat history; previous turns were already
               ;; fontified at their own end-of-stream.
               (eca-chat--font-lock-ensure
                (or eca-chat--last-user-message-pos (point-min))
                (point-max))
               (eca-chat--refresh-copy-scopes)
               ;; Table align/beautify default to scanning from
               ;; `eca-chat--last-user-message-pos' when called with
               ;; no argument, scoping work to the current turn.
               (eca-chat--align-tables)
               (eca-chat--beautify-tables)
               (eca-chat--set-chat-loading session nil)
               (eca-chat--refresh-progress chat-buffer)
               (eca-chat--send-steered-prompt session)
               (eca-chat--send-queued-prompt session)
               (run-hooks 'eca-chat-finished-hook))
              (_
               ;; Idempotent UI cleanup for a `finished' arriving while
               ;; `chat-loading' is nil — e.g. server-driven progress not
               ;; started via `eca-chat--send-prompt', or after the 10s
               ;; stopping safety-timer already cleared the flag.  We still
               ;; clear the visible spinner / progress text and stop the
               ;; elapsed timers, but intentionally skip the trailing
               ;; newline, fontify and queued/steered-prompt dispatch so a
               ;; duplicate `finished' (the case 19aa392 guarded against)
               ;; still cannot insert a second newline or re-trigger a
               ;; queued prompt.
               (setq-local eca-chat--progress-text "")
               (eca-chat--spinner-stop)
               (eca-chat--tool-call-elapsed-stop-all)
               (eca-chat--refresh-progress chat-buffer)))))))
      ("usage"
       (progn
         (if parent-tool-call-id
             ;; Subagent usage — store and refresh the tool call label
             (let ((session-tokens (plist-get content :sessionTokens))
                   (context-limit (plist-get (plist-get content :limit) :context)))
               (puthash parent-tool-call-id
                        (list :session-tokens session-tokens :context-limit context-limit)
                        eca-chat--subagent-usage)
               (eca-chat--refresh-subagent-usage-label parent-tool-call-id))
           (setq-local eca-chat--message-input-tokens  (plist-get content :messageInputTokens))
           (setq-local eca-chat--message-output-tokens (plist-get content :messageOutputTokens))
           (setq-local eca-chat--session-tokens        (plist-get content :sessionTokens))
           (setq-local eca-chat--session-limit-context (plist-get (plist-get content :limit) :context))
           (setq-local eca-chat--session-limit-output  (plist-get (plist-get content :limit) :output))
           (setq-local eca-chat--message-cost          (plist-get content :messageCost))
           (setq-local eca-chat--session-cost          (plist-get content :sessionCost))
           (setq-local eca-chat--session-auto-compact-percentage (plist-get content :autoCompactPercentage))
           (setq-local eca-chat--context-breakdown     (plist-get content :contextBreakdown)))
         (force-mode-line-update)))
      (_ nil))
    (when approval-acted-on?
      (eca-chat--move-on-from-approval))
    (eca-chat--mark-response-copy-break
     content-type parent-tool-call-id)))