branch: elpa/gptel commit 08014b56671b58f8ed0d6b7a0eedb830406750c2 Author: Psionik K <73710933+psioni...@users.noreply.github.com> Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
gptel: Separate tool confirmation and result insertion * gptel.el (gptel--insert-response, gptel--display-tool-calls, gptel--display-tool-results): Separate `gptel--display-tool-calls' into two functions, one for handling tool confirmations and one for results. This is in preparation for the breaking API change to `gptel-request'. NOTE: Tool result insertion into buffers is broken as of this commit. Adjust the predicates for handling tools in `gptel--insert-response'. * gptel-curl.el (gptel-curl--stream-insert-response): Ditto. --- gptel-curl.el | 4 +- gptel-transient.el | 2 + gptel.el | 222 +++++++++++++++++++++++++++-------------------------- 3 files changed, 120 insertions(+), 108 deletions(-) diff --git a/gptel-curl.el b/gptel-curl.el index eae18229a6..7972e6e68e 100644 --- a/gptel-curl.el +++ b/gptel-curl.el @@ -258,7 +258,9 @@ See `gptel--url-get-response' for details." (insert response) (run-hooks 'gptel-post-stream-hook))))) ((pred consp) - (gptel--display-tool-calls response info)))) + (gptel--display-tool-calls response info)) + (_ ; placeholder for later condition + (gptel--display-tool-results response info)))) (defun gptel-curl--stream-filter (process output) (let* ((fsm (alist-get process gptel--request-alist)) diff --git a/gptel-transient.el b/gptel-transient.el index 8c455d807e..db89c86abe 100644 --- a/gptel-transient.el +++ b/gptel-transient.el @@ -1230,6 +1230,7 @@ This sets the variable `gptel-include-tool-results', which see." (lambda (resp info) (cond ((stringp resp) (message "%s response: %s" backend-name resp)) + ;; XXX Check types ((consp resp) (gptel--display-tool-calls resp info 'minibuffer)) ((and (null resp) (plist-get info :error)) (message "%s response error: %s" @@ -1242,6 +1243,7 @@ This sets the variable `gptel-include-tool-results', which see." ((stringp resp) (kill-new resp) (message "%s response: \"%s\" copied to kill-ring." backend-name (truncate-string-to-width resp 30))) + ;; XXX Check types ((consp resp) (gptel--display-tool-calls resp info 'minibuffer)) ((and (null resp) (plist-get info :error)) (message "%s response error: %s" backend-name diff --git a/gptel.el b/gptel.el index ed2828e2e7..f76a8cd810 100644 --- a/gptel.el +++ b/gptel.el @@ -2359,8 +2359,10 @@ See `gptel--url-get-response' for details." (plist-put info :tracking-marker (setq tracking-marker (point-marker))) ;; for uniformity with streaming responses (set-marker-insertion-type tracking-marker t))))) - ((pred consp) ;tool call or tool result? - (gptel--display-tool-calls response info))))) + ((pred consp) + (gptel--display-tool-calls response info)) + (_ + (gptel--display-tool-results response info))))) (defun gptel--create-prompt (&optional prompt-end) "Return a full conversation prompt from the contents of this buffer. @@ -2722,111 +2724,117 @@ RESPONSE is for tool call results. INFO contains the state of the request." (let* ((start-marker (plist-get info :position)) (tracking-marker (plist-get info :tracking-marker))) - (if (cl-typep (caar response) 'gptel-tool) ;tool calls - ;; pending tool calls look like ((tool callback args) ...) - (with-current-buffer (plist-get info :buffer) - (if use-minibuffer ;prompt for confirmation from the minibuffer - (let* ((minibuffer-allow-text-properties t) - (backend-name (gptel-backend-name (plist-get info :backend))) - (prompt (format "%s wants to run " backend-name))) - (map-y-or-n-p - (lambda (tool-call-spec) - (concat prompt (propertize (gptel-tool-name (car tool-call-spec)) - 'face 'font-lock-keyword-face) - ": ")) - (lambda (tcs) (gptel--accept-tool-calls (list tcs) nil)) - response '("tool call" "tool calls" "run") - `((?i ,(lambda (_) (save-window-excursion - (with-selected-window - (gptel--inspect-fsm gptel--fsm-last) - (goto-char (point-min)) - (when (search-forward-regexp "^:tool-use" nil t) - (forward-line 0) (hl-line-highlight)) - (use-local-map - (make-composed-keymap - (define-keymap "q" (lambda () (interactive) - (quit-window) - (exit-recursive-edit))) - (current-local-map))) - (recursive-edit) nil))) - "inspect call(s)")))) - ;; Prompt for confirmation from the chat buffer - (let* ((backend-name (gptel-backend-name (plist-get info :backend))) - (actions-string - (concat (propertize "Run tools: " 'face 'font-lock-string-face) - (propertize "C-c C-c" 'face 'help-key-binding) - (propertize ", Cancel request: " 'face 'font-lock-string-face) - (propertize "C-c C-k" 'face 'help-key-binding) - (propertize ", Inspect: " 'face 'font-lock-string-face) - (propertize "C-c C-i" 'face 'help-key-binding))) - (confirm-strings - (list (concat "\n" actions-string - (propertize "\n" 'face '(:inherit font-lock-string-face - :underline t :extend t)) - (format (propertize "\n%s wants to run:\n" - 'face 'font-lock-string-face) - backend-name)))) - ;; FIXME(tool) use a wrapper instead of a manual text-property search, - ;; this is fragile - (ov-start (save-excursion - (goto-char start-marker) - (text-property-search-backward 'gptel 'response) - (point))) - (ov (or (cdr-safe (get-char-property-and-overlay - start-marker 'gptel-tool)) - (make-overlay ov-start (or tracking-marker start-marker))))) - ;; If the cursor is at the overlay-end, it ends up outside, so move it back - (unless tracking-marker - (when (= (point) start-marker) (ignore-errors (backward-char)))) - (pcase-dolist (`(,tool-spec ,arg-values _) response) - (push (gptel--format-tool-call (gptel-tool-name tool-spec) arg-values) - confirm-strings)) - (push (concat (propertize "\n" 'face '(:inherit font-lock-string-face - :underline t :extend t))) - confirm-strings) - ;; Add confirmation prompt to the overlay - (overlay-put ov 'after-string - (apply #'concat (nreverse confirm-strings))) - (overlay-put ov 'mouse-face 'highlight) - (overlay-put ov 'gptel-tool response) - (overlay-put ov 'help-echo - (concat "Tool call(s) requested: " actions-string)) - (overlay-put ov 'keymap - (define-keymap - "<mouse-1>" #'gptel--dispatch-tool-calls - "C-c C-c" #'gptel--accept-tool-calls - "C-c C-k" #'gptel--reject-tool-calls - "C-c C-i" - (lambda () (interactive) - (with-selected-window - (gptel--inspect-fsm gptel--fsm-last) - (goto-char (point-min)) - (when (search-forward-regexp "^:tool-use" nil t) - (forward-line 0) - (hl-line-highlight))))))))) - ;; finished tool call results look like ((name args result) ...) - ;; Insert tool results - (when gptel-include-tool-results - (with-current-buffer (marker-buffer start-marker) - (cl-loop - for (name args result) in response - with include-names = - (mapcar #'gptel-tool-name - (cl-remove-if-not #'gptel-tool-include (plist-get info :tools))) - if (or (eq gptel-include-tool-results t) (member name include-names)) - do (funcall - (plist-get info :callback) - (if (derived-mode-p 'org-mode) - (concat "\n:TOOL_CALL:\n" (gptel--format-tool-call name args) - "\n" (gptel--to-string result) "\n:END:\n") - (concat "\n```\n" name "\n" (gptel--to-string result) "\n```")) - info) - (when (derived-mode-p 'org-mode) ;fold drawer - (ignore-errors - (save-excursion - (goto-char (plist-get info :tracking-marker)) - (forward-line -1) ;org-fold-hide-drawer-toggle requires Emacs 29.1 - (when (looking-at "^:END:") (org-cycle))))))))))) + (with-current-buffer (plist-get info :buffer) + (if use-minibuffer ;prompt for confirmation from the minibuffer + (let* ((minibuffer-allow-text-properties t) + (backend-name (gptel-backend-name (plist-get info :backend))) + (prompt (format "%s wants to run " backend-name))) + (map-y-or-n-p + (lambda (tool-call-spec) + (concat prompt (propertize (gptel-tool-name (car tool-call-spec)) + 'face 'font-lock-keyword-face) + ": ")) + (lambda (tcs) (gptel--accept-tool-calls (list tcs) nil)) + response '("tool call" "tool calls" "run") + `((?i ,(lambda (_) (save-window-excursion + (with-selected-window + (gptel--inspect-fsm gptel--fsm-last) + (goto-char (point-min)) + (when (search-forward-regexp "^:tool-use" nil t) + (forward-line 0) (hl-line-highlight)) + (use-local-map + (make-composed-keymap + (define-keymap "q" (lambda () (interactive) + (quit-window) + (exit-recursive-edit))) + (current-local-map))) + (recursive-edit) nil))) + "inspect call(s)")))) + ;; Prompt for confirmation from the chat buffer + (let* ((backend-name (gptel-backend-name (plist-get info :backend))) + (actions-string + (concat (propertize "Run tools: " 'face 'font-lock-string-face) + (propertize "C-c C-c" 'face 'help-key-binding) + (propertize ", Cancel request: " 'face 'font-lock-string-face) + (propertize "C-c C-k" 'face 'help-key-binding) + (propertize ", Inspect: " 'face 'font-lock-string-face) + (propertize "C-c C-i" 'face 'help-key-binding))) + (confirm-strings + (list (concat "\n" actions-string + (propertize "\n" 'face '(:inherit font-lock-string-face + :underline t :extend t)) + (format (propertize "\n%s wants to run:\n" + 'face 'font-lock-string-face) + backend-name)))) + ;; FIXME(tool) use a wrapper instead of a manual text-property search, + ;; this is fragile + (ov-start (save-excursion + (goto-char start-marker) + (text-property-search-backward 'gptel 'response) + (point))) + (ov (or (cdr-safe (get-char-property-and-overlay + start-marker 'gptel-tool)) + (make-overlay ov-start (or tracking-marker start-marker))))) + ;; If the cursor is at the overlay-end, it ends up outside, so move it back + (unless tracking-marker + (when (= (point) start-marker) (ignore-errors (backward-char)))) + (pcase-dolist (`(,tool-spec ,arg-values _) response) + (push (gptel--format-tool-call (gptel-tool-name tool-spec) arg-values) + confirm-strings)) + (push (concat (propertize "\n" 'face '(:inherit font-lock-string-face + :underline t :extend t))) + confirm-strings) + ;; Add confirmation prompt to the overlay + (overlay-put ov 'after-string + (apply #'concat (nreverse confirm-strings))) + (overlay-put ov 'mouse-face 'highlight) + (overlay-put ov 'gptel-tool response) + (overlay-put ov 'help-echo + (concat "Tool call(s) requested: " actions-string)) + (overlay-put ov 'keymap + (define-keymap + "<mouse-1>" #'gptel--dispatch-tool-calls + "C-c C-c" #'gptel--accept-tool-calls + "C-c C-k" #'gptel--reject-tool-calls + "C-c C-i" + (lambda () (interactive) + (with-selected-window + (gptel--inspect-fsm gptel--fsm-last) + (goto-char (point-min)) + (when (search-forward-regexp "^:tool-use" nil t) + (forward-line 0) + (hl-line-highlight))))))))))) + +(defun gptel--display-tool-results (response info) + "Display tool results. +RESPONSE is + + ((name args result) ...) + +for tool call results. INFO contains the state of the request." + (let* ((start-marker (plist-get info :position)) + (tracking-marker (plist-get info :tracking-marker))) + (when gptel-include-tool-results + (with-current-buffer (marker-buffer start-marker) + (cl-loop + for (name args result) in response + with include-names = + (mapcar #'gptel-tool-name + (cl-remove-if-not #'gptel-tool-include (plist-get info :tools))) + if (or (eq gptel-include-tool-results t) (member name include-names)) + do (funcall + (plist-get info :callback) + (if (derived-mode-p 'org-mode) + (concat "\n:TOOL_CALL:\n" (gptel--format-tool-call name args) + "\n" (gptel--to-string result) "\n:END:\n") + (concat "\n```\n" name "\n" (gptel--to-string result) "\n```")) + info) + (when (derived-mode-p 'org-mode) ;fold drawer + (ignore-errors + (save-excursion + (goto-char (plist-get info :tracking-marker)) + (forward-line -1) ;org-fold-hide-drawer-toggle requires Emacs 29.1 + (when (looking-at "^:END:") (org-cycle)))))))))) (defun gptel--format-tool-call (name arg-values) "Format a tool call for display in the buffer.