Skip to content

Add support for switching between thought/reasoning levels, or, populate configOptions on the session state #439

@utsahi

Description

@utsahi

Checklist

  • I agree to communicate with the author myself (not AI-generated).
  • I've read the README's Filing issues section.
  • I'm running the latest versions (fill in below).
    • agent-shell version:
    • acp.el version:
    • ACP package (e.g. claude-agent-acp) version:
    • Agent CLI (e.g. claude, gemini) version:
  • For requesting new agent support, I'm including a link to the ACP-capable agent or related ACP package.
  • For issues, I'm including ACP traffic (as per README).

Feature requests:

  1. Populate the configOptions data on the session state https://agentclientprotocol.com/protocol/schema#param-config-options which will enable users to write custom handlers that act upon the available options published by the ACP server.
  2. Add native support for switching between thought/reasoning/effort levels.

Example

The patch below

  1. Populates the config-options on the session object
  2. Adds the current reasoning effort to the header line. However, this part of the patch probably only works on GitHub Copilot ACP since the config option name ''reasoning_effort" seems to be specific to Copilot/OpenAI. However, this detail could be abstracted away by adding a new function/handler that can be overridden by the implementation.
diff --git a/agent-shell.el b/agent-shell.el
index c2d25f6..2c823d5 100644
--- a/agent-shell.el
+++ b/agent-shell.el
@@ -684,7 +684,8 @@ OUTGOING-REQUEST-DECORATOR (passed through to `acp-make-client')."
         (cons :set-session-mode nil)
         (cons :session (list (cons :id nil)
                              (cons :mode-id nil)
-                             (cons :modes nil)))
+                             (cons :modes nil)
+                             (cons :config-options nil)))
         (cons :last-entry-type nil)
         (cons :chunked-group-count 0)
         (cons :request-count 0)
@@ -1653,10 +1654,12 @@ COMMAND, when present, may be a shell command string or an argv vector."
              ;; Note: No need to set :last-entry-type as no text was inserted.
              (agent-shell--update-header-and-mode-line)))
           ((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "config_option_update")
-           ;; Silently handle config option updates (e.g., from set_model/set_mode)
-           ;; These are informational notifications that don't require user-visible output
-           ;; Note: No need to set :last-entry-type as no text was inserted.
-           nil)
+           (let ((config-options (map-nested-elt acp-notification '(params update configOptions))))
+             (when config-options
+               (let ((updated-session (map-elt state :session)))
+                 (map-put! updated-session :config-options config-options)
+                 (map-put! state :session updated-session)))
+             (agent-shell--update-header-and-mode-line)))
           ((equal (map-nested-elt acp-notification '(params update sessionUpdate)) "usage_update")
            ;; Extract context window and cost information
            (agent-shell--update-usage-from-notification
@@ -3056,11 +3059,16 @@ The model contains all inputs needed to render the graphical header."
                       (or (agent-shell--resolve-session-mode-name
                            mode-id
                            (agent-shell--get-available-modes state))
-                          mode-id))))
+                          mode-id)))
+         (reasoning-effort (map-elt
+                            (seq-find (lambda (o) (string-equal "reasoning_effort" (map-elt o 'id)))
+                                      (map-nested-elt state '(:session :config-options)))
+                            'currentValue)))
     `((:buffer-name . ,(map-nested-elt state '(:agent-config :buffer-name)))
       (:icon-name . ,(map-nested-elt state '(:agent-config :icon-name)))
       (:model-id . ,(map-nested-elt state '(:session :model-id)))
       (:model-name . ,model-name)
+      (:reasoning-effort . ,reasoning-effort)
       (:mode-id . ,mode-id)
       (:mode-name . ,mode-name)
       (:directory . ,default-directory)
@@ -3097,12 +3105,15 @@ BINDINGS is a list of alists defining key bindings to display, each with:
   (unless state
     (error "STATE is required"))
   (let* ((header-model (agent-shell--make-header-model state :qualifier qualifier :bindings bindings))
-         (text-header (format " %s%s%s @ %s%s%s%s"
+         (text-header (format " %s%s%s%s @ %s%s%s%s"
                               (propertize (concat (map-elt header-model :buffer-name) " Agent")
                                           'font-lock-face 'font-lock-variable-name-face)
                               (if (map-elt header-model :model-name)
                                   (concat " ➤ " (propertize (map-elt header-model :model-name) 'font-lock-face 'font-lock-negation-char-face))
                                 "")
+                              (if (map-elt header-model :reasoning-effort)
+                                  (concat " (" (propertize (map-elt header-model :reasoning-effort) 'font-lock-face 'font-lock-negation-char-face) ")")
+                                "")
                               (if (map-elt header-model :mode-name)
                                   (concat " ➤ " (propertize (map-elt header-model :mode-name) 'font-lock-face 'font-lock-type-face))
                                 "")
@@ -3188,7 +3199,11 @@ BINDINGS is a list of alists defining key bindings to display, each with:
                                                           (dom-node 'tspan
                                                                     `((fill . ,(face-attribute 'font-lock-negation-char-face :foreground))
                                                                       (dx . "8"))
-                                                                    (map-elt header-model :model-name))))
+                                                                    (format "%s%s" 
+                                                                            (map-elt header-model :model-name)
+                                                                            (if (map-elt header-model :reasoning-effort)
+                                                                                (concat " (" (map-elt header-model :reasoning-effort) ")")
+                                                                              "")))))
                                       ;; Session mode (optional)
                                       (when (map-elt header-model :mode-id)
                                         ;; Add separator arrow
@@ -4051,7 +4066,8 @@ Falls back to latest session in batch mode (e.g. tests)."
                                                    `((:model-id . ,(map-elt model 'modelId))
                                                      (:name . ,(map-elt model 'name))
                                                      (:description . ,(map-elt model 'description))))
-                                                 (map-nested-elt acp-response '(models availableModels)))))))
+                                                 (map-nested-elt acp-response '(models availableModels))))
+                           (cons :config-options (map-elt acp-response 'configOptions)))))
 
 (cl-defun agent-shell--finalize-session-init (&key on-session-init)
   "Finalize session initialization and invoke ON-SESSION-INIT."
@@ -4108,7 +4124,8 @@ Falls back to latest session in batch mode (e.g. tests)."
                                                                   `((:model-id . ,(map-elt model 'modelId))
                                                                     (:name . ,(map-elt model 'name))
                                                                     (:description . ,(map-elt model 'description))))
-                                                                (map-nested-elt acp-response '(models availableModels))))))
+                                                                (map-nested-elt acp-response '(models availableModels))))
+                                          (cons :config-options (map-elt acp-response 'configOptions))))
                  (agent-shell--update-fragment
                   :state agent-shell--state
                   :block-id "starting"

A generic acp-*-set-config-option function can be added to acp.el that the users can call to set the effort level. E.g., I'm using the following approach

(let ((request `((:method . "session/set_config_option")
                     (:params . ((sessionId . ,session-id)
                                 (configId . "reasoning_effort")
                                 (value . ,choice))))))
      (acp-send-request
       :client client
       :request request
       :on-success (lambda (_resp) (message "reasoning_effort set -> %s" choice))
       :on-failure (lambda (err) (message "Failed to set reasoning_effort: %S" err))))

If the "reasoning_effort" setting name can be abstracted, a dedicated command similar to agent-shell-set-session-model can be added to set thought level.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions