gptel: Remove aio dependency

* gptel.el (gptel-send, gptel--insert-response,
gptel--url-get-response): Remove aio dependency, turn aio-defuns
into regular functions.  This requires splitting `gptel-send' into
"before response" and "after response" functions, but the ability
to debug the code fully is worth the inconvenience.  The new "after
response" function is `gptel--insert-response'.

* gptel-curl.el (gptel-curl--sentinel, gptel-curl-get-response):
Turn aio-defuns into regular functions.
This commit is contained in:
Karthik Chikmagalur 2023-03-17 23:46:30 -07:00
parent 8a6ef565f0
commit 040baad910
2 changed files with 61 additions and 49 deletions

View file

@ -32,7 +32,6 @@
(require 'subr-x))
(require 'map)
(require 'json)
(require 'aio)
(defvar gptel-curl--process-alist nil
"Alist of active GPTel curl requests.")
@ -67,26 +66,24 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
(nreverse (cons url args))))
;;;###autoload
(defun gptel-curl-get-response (prompts)
"Retrieve response to PROMPTS."
(defun gptel-curl-get-response (info)
"Retrieve response to prompt in INFO.
INFO is a plist with the following keys:
- :prompt (the prompt being sent)
- :gptel-buffer (the gptel buffer)
- :insert-marker (marker at which to insert the response)."
(with-current-buffer (generate-new-buffer "*gptel-curl*")
(let* ((token (md5 (format "%s%s%s%s"
(random) (emacs-pid) (user-full-name)
(recent-keys))))
(args (gptel-curl--get-args prompts token))
(args (gptel-curl--get-args (plist-get info :prompt) token))
(process (apply #'start-process "gptel-curl" (current-buffer)
"curl" args))
(promise (aio-promise))
(cb (lambda (result)
(aio-resolve promise (lambda () result))
(setf (alist-get process
gptel-curl--process-alist nil 'remove)
nil))))
(prog1 promise
"curl" args)))
(set-process-query-on-exit-flag process nil)
(setf (alist-get process gptel-curl--process-alist)
(list :callback cb :token token))
(set-process-sentinel process #'gptel-curl--sentinel)))))
(nconc (list :token token) info))
(set-process-sentinel process #'gptel-curl--sentinel))))
(defun gptel-curl--sentinel (process status)
"Process sentinel for GPTel curl requests.
@ -96,13 +93,14 @@ PROCESS and STATUS are process parameters."
(when gptel--debug
(with-current-buffer proc-buf
(clone-buffer "*gptel-error*" 'show)))
(if-let* ((ok-p (equal status "finished\n"))
(if-let* (((equal status "finished\n"))
(proc-info (alist-get process gptel-curl--process-alist))
(proc-token (plist-get proc-info :token))
(content (gptel-curl--parse-response proc-buf proc-token)))
(funcall (plist-get proc-info :callback) content)
(response (gptel-curl--parse-response proc-buf proc-token)))
(gptel--insert-response response proc-info)
;; Failed
(funcall (plist-get proc-info :callback) nil))
(gptel--insert-response (list :content nil :status status) proc-info))
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
(kill-buffer proc-buf)))
(defun gptel-curl--parse-response (buf token)

View file

@ -4,7 +4,7 @@
;; Author: Karthik Chikmagalur
;; Version: 0.10
;; Package-Requires: ((emacs "27.1") (aio "1.0") (transient "0.3.7"))
;; Package-Requires: ((emacs "27.1") (transient "0.3.7"))
;; Keywords: convenience
;; URL: https://github.com/karthink/gptel
@ -33,9 +33,6 @@
;; - You need an OpenAI API key. Set the variable `gptel-api-key' to the key or to
;; a function of no arguments that returns the key.
;;
;; - If installing manually: Install the package `emacs-aio' using `M-x package-install'
;; or however you install packages.
;;
;; - Not required but recommended: Install `markdown-mode'.
;;
;; Usage:
@ -56,7 +53,7 @@
(require 'subr-x)
(require 'cl-lib))
(require 'aio)
(require 'url)
(require 'json)
(require 'map)
(require 'text-property-search)
@ -150,8 +147,13 @@ return the transformed string."
;; TODO: Handle read-only buffers. Should we spawn a new buffer automatically?
;; TODO: Handle multiple requests(#15). (Only one request from one buffer at a time?)
(aio-defun gptel-send (&optional arg)
"Submit this prompt to ChatGPT."
;; TODO: Since we capture a marker for the insertion location, `gptel-buffer' no
;; longer needs to be recorded
(defun gptel-send (&optional arg)
"Submit this prompt to ChatGPT.
With prefix arg ARG activate a transient menu with more options
instead."
(interactive "P")
(if (and arg (require 'gptel-transient nil t))
(call-interactively #'gptel-send-menu)
@ -162,14 +164,23 @@ return the transformed string."
(set-marker (make-marker) (region-end))
(point-marker)))
(gptel-buffer (current-buffer))
(full-prompt (gptel--create-prompt response-pt))
(response (aio-await
(full-prompt (gptel--create-prompt response-pt)))
(funcall
(if gptel-use-curl
#'gptel-curl-get-response #'gptel--url-get-response)
full-prompt)))
(content-str (plist-get response :content))
(status-str (plist-get response :status)))
(list :prompt full-prompt
:gptel-buffer gptel-buffer
:insert-marker response-pt)))))
(defun gptel--insert-response (response info)
"Insert RESPONSE from ChatGPT into the gptel buffer.
INFO is a plist containing information relevant to this buffer.
See `gptel--url-get-response' for details."
(let* ((content-str (plist-get response :content))
(status-str (plist-get response :status))
(gptel-buffer (plist-get info :gptel-buffer))
(response-pt (plist-get info :insert-marker)))
(if content-str
(with-current-buffer gptel-buffer
(setq content-str (gptel--transform-response
@ -178,9 +189,6 @@ return the transformed string."
(put-text-property 0 (length content-str) 'gptel 'response content-str)
(message "Querying ChatGPT... done.")
(goto-char response-pt)
(display-buffer (current-buffer)
'((display-buffer-reuse-window
display-buffer-use-some-window)))
(unless (bobp) (insert-before-markers-and-inherit "\n\n"))
(if gptel-playback
(gptel--playback gptel-buffer content-str response-pt)
@ -192,7 +200,7 @@ return the transformed string."
(gptel--update-header-line " Ready" 'success))))
(goto-char (- (point) 2)))
(gptel--update-header-line
(format " Response Error: %s" status-str) 'error)))))
(format " Response Error: %s" status-str) 'error))))
(defun gptel--create-prompt (&optional prompt-end)
"Return a full conversation prompt from the contents of this buffer.
@ -273,10 +281,13 @@ BUFFER is the interaction buffer for ChatGPT."
('org-mode (gptel--convert-markdown->org content))
(_ content)))
(aio-defun gptel--url-get-response (prompts)
"Fetch response for PROMPTS from ChatGPT.
(defun gptel--url-get-response (info)
"Fetch response to prompt in INFO from ChatGPT.
Return the message received."
INFO is a plist with the following keys:
- :prompt (the prompt being sent)
- :gptel-buffer (the gptel buffer)
- :insert-marker (marker at which to insert the response)."
(let* ((inhibit-message t)
(message-log-max nil)
(api-key
@ -289,13 +300,16 @@ Return the message received."
`(("Content-Type" . "application/json")
("Authorization" . ,(concat "Bearer " api-key))))
(url-request-data
(encode-coding-string (json-encode (gptel--request-data prompts)) 'utf-8)))
(pcase-let ((`(,_ . ,response-buffer)
(aio-await
(aio-url-retrieve "https://api.openai.com/v1/chat/completions"))))
(prog1
(gptel--url-parse-response response-buffer)
(kill-buffer response-buffer)))))
(encode-coding-string
(json-encode (gptel--request-data (plist-get info :prompt)))
'utf-8)))
(url-retrieve "https://api.openai.com/v1/chat/completions"
(lambda (_)
(let ((response
(gptel--url-parse-response (current-buffer))))
(gptel--insert-response response info)
(kill-buffer)))
nil t nil)))
(defun gptel--url-parse-response (response-buffer)
"Parse response in RESPONSE-BUFFER."