2023-03-08 00:52:48 -08:00
|
|
|
;;; gptel-curl.el --- Curl support for GPTel -*- lexical-binding: t; -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2023 Karthik Chikmagalur
|
|
|
|
|
|
|
|
;; Author: Karthik Chikmagalur;; <karthikchikmagalur@gmail.com>
|
|
|
|
;; Keywords: convenience
|
2023-03-08 19:17:14 -08:00
|
|
|
|
|
|
|
;; SPDX-License-Identifier: GPL-3.0-or-later
|
2023-03-08 00:52:48 -08:00
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;; Curl support for GPTel. Utility functions.
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
2023-03-08 19:17:14 -08:00
|
|
|
(require 'gptel)
|
|
|
|
|
2023-03-08 00:52:48 -08:00
|
|
|
(eval-when-compile
|
2023-06-05 23:17:55 -05:00
|
|
|
(require 'cl-lib)
|
2023-03-08 00:52:48 -08:00
|
|
|
(require 'subr-x))
|
|
|
|
(require 'map)
|
|
|
|
(require 'json)
|
|
|
|
|
2023-03-08 19:23:17 -08:00
|
|
|
(defvar gptel-curl--process-alist nil
|
2023-03-08 00:52:48 -08:00
|
|
|
"Alist of active GPTel curl requests.")
|
|
|
|
|
2023-03-08 19:23:17 -08:00
|
|
|
(defun gptel-curl--get-args (prompts token)
|
2023-03-08 00:52:48 -08:00
|
|
|
"Produce list of arguments for calling Curl.
|
|
|
|
|
|
|
|
PROMPTS is the data to send, TOKEN is a unique identifier."
|
2023-06-05 23:17:55 -05:00
|
|
|
(let* ((url (format "https://%s/v1/chat/completions" gptel-host))
|
2023-03-08 00:52:48 -08:00
|
|
|
(data (encode-coding-string
|
2023-03-08 19:20:00 -08:00
|
|
|
(json-encode (gptel--request-data prompts))
|
2023-03-08 00:52:48 -08:00
|
|
|
'utf-8))
|
|
|
|
(headers
|
|
|
|
`(("Content-Type" . "application/json")
|
2023-03-18 08:41:25 +01:00
|
|
|
("Authorization" . ,(concat "Bearer " (gptel--api-key))))))
|
2023-06-05 23:17:55 -05:00
|
|
|
(append
|
|
|
|
(list "--location" "--silent" "--compressed" "--disable"
|
|
|
|
(format "-X%s" "POST")
|
|
|
|
(format "-w(%s . %%{size_header})" token)
|
|
|
|
(format "-m%s" 60)
|
|
|
|
"-D-"
|
|
|
|
(format "-d%s" data))
|
|
|
|
(when (not (string-empty-p gptel-proxy))
|
|
|
|
(list "--proxy" gptel-proxy
|
|
|
|
"--proxy-negotiate"
|
|
|
|
"--proxy-user" ":"))
|
|
|
|
(cl-loop for (key . val) in headers
|
|
|
|
collect (format "-H%s: %s" key val))
|
|
|
|
(list url))))
|
2023-03-08 00:52:48 -08:00
|
|
|
|
2023-04-04 22:19:37 -07:00
|
|
|
;;TODO: The :transformer argument here is an alternate implementation of
|
|
|
|
;;`gptel-response-filter-functions'. The two need to be unified.
|
2023-03-08 19:17:14 -08:00
|
|
|
;;;###autoload
|
2023-03-31 19:37:25 -07:00
|
|
|
(defun gptel-curl-get-response (info &optional callback)
|
2023-03-17 23:46:30 -07:00
|
|
|
"Retrieve response to prompt in INFO.
|
|
|
|
|
|
|
|
INFO is a plist with the following keys:
|
|
|
|
- :prompt (the prompt being sent)
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
- :buffer (the gptel buffer)
|
|
|
|
- :position (marker at which to insert the response).
|
2023-03-31 19:37:25 -07:00
|
|
|
|
|
|
|
Call CALLBACK with the response and INFO afterwards. If omitted
|
|
|
|
the response is inserted into the current buffer after point."
|
2023-04-12 22:10:54 -07:00
|
|
|
(let* ((token (md5 (format "%s%s%s%s"
|
|
|
|
(random) (emacs-pid) (user-full-name)
|
|
|
|
(recent-keys))))
|
|
|
|
(args (gptel-curl--get-args (plist-get info :prompt) token))
|
|
|
|
(process (apply #'start-process "gptel-curl"
|
|
|
|
(generate-new-buffer "*gptel-curl*") "curl" args)))
|
|
|
|
(with-current-buffer (process-buffer process)
|
2023-03-17 23:46:30 -07:00
|
|
|
(set-process-query-on-exit-flag process nil)
|
|
|
|
(setf (alist-get process gptel-curl--process-alist)
|
2023-03-31 19:37:25 -07:00
|
|
|
(nconc (list :token token
|
2023-04-01 02:36:10 -07:00
|
|
|
:callback (or callback
|
2023-04-06 16:35:51 -07:00
|
|
|
(if gptel-stream
|
|
|
|
#'gptel-curl--stream-insert-response
|
2023-04-04 22:19:37 -07:00
|
|
|
#'gptel--insert-response))
|
|
|
|
:transformer (when (or (eq gptel-default-mode 'org-mode)
|
|
|
|
(eq (buffer-local-value
|
|
|
|
'major-mode
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(plist-get info :buffer))
|
2023-04-04 22:19:37 -07:00
|
|
|
'org-mode))
|
2023-04-06 16:35:51 -07:00
|
|
|
(gptel--stream-convert-markdown->org)))
|
2023-03-31 19:37:25 -07:00
|
|
|
info))
|
2023-04-06 16:35:51 -07:00
|
|
|
(if gptel-stream
|
|
|
|
(progn (set-process-sentinel process #'gptel-curl--stream-cleanup)
|
|
|
|
(set-process-filter process #'gptel-curl--stream-filter))
|
2023-04-01 02:36:10 -07:00
|
|
|
(set-process-sentinel process #'gptel-curl--sentinel)))))
|
|
|
|
|
2023-05-19 22:26:38 -07:00
|
|
|
(defun gptel-abort (buf)
|
|
|
|
"Stop any active gptel process associated with the current buffer."
|
|
|
|
(interactive (list (current-buffer)))
|
|
|
|
(unless gptel-use-curl
|
|
|
|
(user-error "Cannot stop a `url-retrieve' request!"))
|
|
|
|
(if-let* ((proc-attrs
|
|
|
|
(cl-find-if
|
|
|
|
(lambda (proc-list)
|
|
|
|
(eq (plist-get (cdr proc-list) :buffer) buf))
|
|
|
|
gptel-curl--process-alist))
|
|
|
|
(proc (car proc-attrs)))
|
|
|
|
(progn
|
|
|
|
(setf (alist-get proc gptel-curl--process-alist nil 'remove) nil)
|
|
|
|
(set-process-sentinel proc #'ignore)
|
|
|
|
(delete-process proc)
|
|
|
|
(kill-buffer (process-buffer proc))
|
|
|
|
(with-current-buffer buf
|
|
|
|
(when gptel-mode (gptel--update-header-line " Ready" 'success)))
|
|
|
|
(message "Stopped gptel request in buffer %S" (buffer-name buf)))
|
|
|
|
(message "No gptel request associated with buffer %S" (buffer-name buf))))
|
|
|
|
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
;; TODO: Separate user-messaging from this function
|
2023-04-06 16:35:51 -07:00
|
|
|
(defun gptel-curl--stream-cleanup (process status)
|
2023-04-01 02:36:10 -07:00
|
|
|
"Process sentinel for GPTel curl requests.
|
|
|
|
|
|
|
|
PROCESS and STATUS are process parameters."
|
|
|
|
(let ((proc-buf (process-buffer process)))
|
|
|
|
(when gptel--debug
|
|
|
|
(with-current-buffer proc-buf
|
|
|
|
(clone-buffer "*gptel-error*" 'show)))
|
|
|
|
(let* ((info (alist-get process gptel-curl--process-alist))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(gptel-buffer (plist-get info :buffer))
|
2023-04-01 02:36:10 -07:00
|
|
|
(tracking-marker (plist-get info :tracking-marker))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(start-marker (plist-get info :position))
|
2023-04-08 09:57:38 -07:00
|
|
|
(http-status (plist-get info :http-status))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(http-msg (plist-get info :status)))
|
2023-04-08 09:57:38 -07:00
|
|
|
(if (equal http-status "200")
|
2023-05-03 13:42:44 -07:00
|
|
|
(progn
|
|
|
|
;; Finish handling response
|
|
|
|
(with-current-buffer (marker-buffer start-marker)
|
|
|
|
(pulse-momentary-highlight-region (+ start-marker 2) tracking-marker)
|
|
|
|
(when gptel-mode (save-excursion (goto-char tracking-marker)
|
|
|
|
(insert "\n\n" (gptel-prompt-string)))))
|
|
|
|
(with-current-buffer gptel-buffer
|
|
|
|
(when gptel-mode (gptel--update-header-line " Ready" 'success))))
|
2023-04-08 09:57:38 -07:00
|
|
|
;; Or Capture error message
|
|
|
|
(with-current-buffer proc-buf
|
|
|
|
(goto-char (point-max))
|
|
|
|
(search-backward (plist-get info :token))
|
|
|
|
(backward-char)
|
|
|
|
(pcase-let* ((`(,_ . ,header-size) (read (current-buffer)))
|
2023-04-08 12:22:36 -07:00
|
|
|
(json-object-type 'plist)
|
2023-04-08 09:57:38 -07:00
|
|
|
(response (progn (goto-char header-size)
|
|
|
|
(condition-case nil (json-read)
|
|
|
|
(json-readtable-error 'json-read-error)))))
|
|
|
|
(cond
|
|
|
|
((plist-get response :error)
|
|
|
|
(let* ((error-plist (plist-get response :error))
|
|
|
|
(error-msg (plist-get error-plist :message))
|
|
|
|
(error-type (plist-get error-plist :type)))
|
2023-04-08 12:22:36 -07:00
|
|
|
(message "ChatGPT error: (%s) %s" http-msg error-msg)
|
|
|
|
(setq http-msg (concat "(" http-msg ") " (string-trim error-type)))))
|
2023-04-08 09:57:38 -07:00
|
|
|
((eq response 'json-read-error)
|
|
|
|
(message "ChatGPT error (%s): Malformed JSON in response." http-msg))
|
|
|
|
(t (message "ChatGPT error (%s): Could not parse HTTP response." http-msg)))))
|
|
|
|
(with-current-buffer gptel-buffer
|
2023-04-01 02:36:10 -07:00
|
|
|
(when gptel-mode
|
2023-04-08 09:57:38 -07:00
|
|
|
(gptel--update-header-line
|
2023-05-03 13:47:15 -07:00
|
|
|
(format " Response Error: %s" http-msg) 'error))))
|
|
|
|
(with-current-buffer gptel-buffer
|
|
|
|
(run-hooks 'gptel-post-response-hook)))
|
2023-04-01 02:36:10 -07:00
|
|
|
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
|
|
|
|
(kill-buffer proc-buf)))
|
|
|
|
|
2023-04-06 16:18:37 -07:00
|
|
|
(defun gptel-curl--stream-insert-response (response info)
|
2023-04-01 02:36:10 -07:00
|
|
|
"Insert streaming RESPONSE from ChatGPT into the gptel buffer.
|
|
|
|
|
|
|
|
INFO is a mutable plist containing information relevant to this buffer.
|
|
|
|
See `gptel--url-get-response' for details."
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(let ((status-str (plist-get response :status))
|
|
|
|
(start-marker (plist-get info :position))
|
2023-04-04 22:19:37 -07:00
|
|
|
(tracking-marker (plist-get info :tracking-marker))
|
|
|
|
(transformer (plist-get info :transformer)))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(when response
|
2023-05-03 13:42:44 -07:00
|
|
|
(with-current-buffer (marker-buffer start-marker)
|
2023-04-01 02:36:10 -07:00
|
|
|
(save-excursion
|
|
|
|
(unless tracking-marker
|
|
|
|
(gptel--update-header-line " Typing..." 'success)
|
|
|
|
(goto-char start-marker)
|
2023-05-03 13:42:44 -07:00
|
|
|
(unless (or (bobp) (plist-get info :in-place))
|
2023-04-09 03:29:48 -07:00
|
|
|
(insert "\n\n"))
|
2023-04-01 02:36:10 -07:00
|
|
|
(setq tracking-marker (set-marker (make-marker) (point)))
|
|
|
|
(set-marker-insertion-type tracking-marker t)
|
|
|
|
(plist-put info :tracking-marker tracking-marker))
|
|
|
|
|
2023-04-04 22:19:37 -07:00
|
|
|
(when transformer
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(setq response (funcall transformer response)))
|
2023-04-04 22:19:37 -07:00
|
|
|
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(put-text-property 0 (length response) 'gptel 'response response)
|
2023-04-01 02:36:10 -07:00
|
|
|
(goto-char tracking-marker)
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(insert response))))))
|
2023-04-01 02:36:10 -07:00
|
|
|
|
2023-04-06 16:35:51 -07:00
|
|
|
(defun gptel-curl--stream-filter (process output)
|
2023-04-01 02:36:10 -07:00
|
|
|
(let* ((content-strs)
|
|
|
|
(proc-info (alist-get process gptel-curl--process-alist)))
|
|
|
|
(with-current-buffer (process-buffer process)
|
|
|
|
;; Insert output
|
|
|
|
(save-excursion
|
|
|
|
(goto-char (process-mark process))
|
|
|
|
(insert output)
|
|
|
|
(set-marker (process-mark process) (point)))
|
|
|
|
|
|
|
|
;; Find HTTP status
|
|
|
|
(unless (plist-get proc-info :http-status)
|
|
|
|
(save-excursion
|
|
|
|
(goto-char (point-min))
|
|
|
|
(when-let* (((not (= (line-end-position) (point-max))))
|
|
|
|
(http-msg (buffer-substring (line-beginning-position)
|
|
|
|
(line-end-position)))
|
|
|
|
(http-status
|
|
|
|
(save-match-data
|
|
|
|
(and (string-match "HTTP/[.0-9]+ +\\([0-9]+\\)" http-msg)
|
|
|
|
(match-string 1 http-msg)))))
|
|
|
|
(plist-put proc-info :http-status http-status)
|
2023-05-03 13:42:44 -07:00
|
|
|
(plist-put proc-info :status (string-trim http-msg))))
|
|
|
|
;; Handle read-only gptel buffer
|
|
|
|
(when (with-current-buffer (plist-get proc-info :buffer)
|
|
|
|
(or buffer-read-only
|
|
|
|
(get-char-property (plist-get proc-info :position) 'read-only)))
|
|
|
|
(message "Buffer is read only, displaying reply in buffer \"*ChatGPT response*\"")
|
|
|
|
(display-buffer
|
|
|
|
(with-current-buffer (get-buffer-create "*ChatGPT response*")
|
|
|
|
(goto-char (point-max))
|
|
|
|
(move-marker (plist-get proc-info :position) (point) (current-buffer))
|
|
|
|
(current-buffer))
|
|
|
|
'((display-buffer-reuse-window
|
|
|
|
display-buffer-pop-up-window)
|
|
|
|
(reusable-frames . visible)))))
|
2023-04-01 02:36:10 -07:00
|
|
|
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(when-let ((http-msg (plist-get proc-info :status))
|
2023-04-01 02:36:10 -07:00
|
|
|
(http-status (plist-get proc-info :http-status)))
|
|
|
|
;; Find data chunk(s) and run callback
|
2023-04-08 09:57:38 -07:00
|
|
|
(when (equal http-status "200")
|
|
|
|
(funcall (or (plist-get proc-info :callback)
|
|
|
|
#'gptel-curl--stream-insert-response)
|
|
|
|
(let* ((json-object-type 'plist)
|
|
|
|
(response) (content-str))
|
|
|
|
(condition-case nil
|
|
|
|
(while (re-search-forward "^data:" nil t)
|
|
|
|
(save-match-data
|
|
|
|
(unless (looking-at " *\\[DONE\\]")
|
|
|
|
(when-let* ((response (json-read))
|
|
|
|
(delta (map-nested-elt
|
|
|
|
response '(:choices 0 :delta)))
|
|
|
|
(content (plist-get delta :content)))
|
|
|
|
(push content content-strs)))))
|
|
|
|
(error
|
|
|
|
(goto-char (match-beginning 0))))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(apply #'concat (nreverse content-strs)))
|
2023-04-08 09:57:38 -07:00
|
|
|
proc-info))))))
|
2023-03-08 00:52:48 -08:00
|
|
|
|
2023-03-08 19:23:17 -08:00
|
|
|
(defun gptel-curl--sentinel (process status)
|
2023-03-08 00:52:48 -08:00
|
|
|
"Process sentinel for GPTel curl requests.
|
|
|
|
|
|
|
|
PROCESS and STATUS are process parameters."
|
|
|
|
(let ((proc-buf (process-buffer process)))
|
2023-03-12 15:40:39 -07:00
|
|
|
(when gptel--debug
|
|
|
|
(with-current-buffer proc-buf
|
|
|
|
(clone-buffer "*gptel-error*" 'show)))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(when-let* (((eq (process-status process) 'exit))
|
|
|
|
(proc-info (alist-get process gptel-curl--process-alist))
|
|
|
|
(proc-token (plist-get proc-info :token))
|
|
|
|
(proc-callback (plist-get proc-info :callback)))
|
|
|
|
(pcase-let ((`(,response ,http-msg ,error)
|
|
|
|
(gptel-curl--parse-response proc-buf proc-token)))
|
|
|
|
(plist-put proc-info :status http-msg)
|
|
|
|
(when error (plist-put proc-info :error error))
|
|
|
|
(funcall proc-callback response proc-info)))
|
2023-03-17 23:46:30 -07:00
|
|
|
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
|
2023-03-08 00:52:48 -08:00
|
|
|
(kill-buffer proc-buf)))
|
|
|
|
|
2023-03-08 19:23:17 -08:00
|
|
|
(defun gptel-curl--parse-response (buf token)
|
2023-03-08 00:52:48 -08:00
|
|
|
"Parse the buffer BUF with curl's response.
|
|
|
|
|
|
|
|
TOKEN is used to disambiguate multiple requests in a single
|
|
|
|
buffer."
|
|
|
|
(with-current-buffer buf
|
|
|
|
(progn
|
|
|
|
(goto-char (point-max))
|
|
|
|
(search-backward token)
|
|
|
|
(backward-char)
|
|
|
|
(pcase-let* ((`(,_ . ,header-size) (read (current-buffer))))
|
|
|
|
;; (if (search-backward token nil t)
|
|
|
|
;; (search-forward ")" nil t)
|
|
|
|
;; (goto-char (point-min)))
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
2023-04-08 12:22:36 -07:00
|
|
|
(if-let* ((http-msg (string-trim
|
|
|
|
(buffer-substring (line-beginning-position)
|
|
|
|
(line-end-position))))
|
2023-03-08 00:52:48 -08:00
|
|
|
(http-status
|
|
|
|
(save-match-data
|
|
|
|
(and (string-match "HTTP/[.0-9]+ +\\([0-9]+\\)" http-msg)
|
|
|
|
(match-string 1 http-msg))))
|
|
|
|
(json-object-type 'plist)
|
|
|
|
(response (progn (goto-char header-size)
|
2023-03-22 18:34:18 -07:00
|
|
|
(condition-case nil
|
|
|
|
(json-read)
|
|
|
|
(json-readtable-error 'json-read-error)))))
|
2023-03-08 00:52:48 -08:00
|
|
|
(cond
|
2023-03-22 18:34:18 -07:00
|
|
|
((equal http-status "200")
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(list (string-trim
|
2023-03-22 18:34:18 -07:00
|
|
|
(map-nested-elt response '(:choices 0 :message :content)))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
http-msg))
|
2023-03-22 18:34:18 -07:00
|
|
|
((plist-get response :error)
|
|
|
|
(let* ((error-plist (plist-get response :error))
|
|
|
|
(error-msg (plist-get error-plist :message))
|
|
|
|
(error-type (plist-get error-plist :type)))
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(list nil (concat "(" http-msg ") " (string-trim error-type)) error-msg)))
|
2023-03-22 18:34:18 -07:00
|
|
|
((eq response 'json-read-error)
|
gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response,
gptel--insert-response, gptel-send):
- Use shorter keys for passing the info plist,
- record errors in the info plist,
- separate user messaging from the callback and more.
- Make the API more functional (i.e. less imperative)
This is in preparation for adding `gptel-request', an API for
defining custom commands.
Note: The streaming filter and callback are mostly unchanged.
Streaming is not planned to be accessible via `gptel-request'.
* gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel,
gptel-curl--stream-filter, gptel-curl--stream-insert-response,
gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
2023-04-08 16:57:38 -07:00
|
|
|
(list nil (concat "(" http-msg ") Malformed JSON in response.")
|
|
|
|
"Malformed JSON in response"))
|
|
|
|
(t (list nil (concat "(" http-msg ") Could not parse HTTP response.")
|
|
|
|
"Could not parse HTTP response.")))
|
|
|
|
(list nil (concat "(" http-msg ") Could not parse HTTP response.")
|
|
|
|
"Could not parse HTTP response."))))))
|
2023-03-08 00:52:48 -08:00
|
|
|
|
|
|
|
(provide 'gptel-curl)
|
|
|
|
;;; gptel-curl.el ends here
|