branch: externals/llm commit b438ae7a72e7304941a6f15df469aa8c3492a0a5 Merge: 1fb8511867 ccfe066654 Author: Andrew Hyatt <ahy...@gmail.com> Commit: GitHub <nore...@github.com>
Merge pull request #35 from r0man/plz Error handling & JSON parsing --- llm-gemini.el | 17 ++-- llm-ollama.el | 17 ++-- llm-openai.el | 2 - llm-request-plz.el | 53 +++++------- llm-vertex.el | 17 ++-- plz-event-source.el | 11 ++- plz-media-type.el | 240 +++++++++++++++++++++++++++++++++++++--------------- plz.el | 1 + 8 files changed, 228 insertions(+), 130 deletions(-) diff --git a/llm-gemini.el b/llm-gemini.el index 9915601b30..7e86b3b112 100644 --- a/llm-gemini.el +++ b/llm-gemini.el @@ -65,7 +65,6 @@ You can get this at https://makersuite.google.com/app/apikey." (let ((buf (current-buffer))) (llm-request-plz-async (llm-gemini--embedding-url provider) :data (llm-gemini--embedding-request provider string) - :media-type '(application/json) :on-success (lambda (data) (llm-request-callback-in-buffer buf vector-callback (llm-gemini--embedding-response-handler data))) @@ -112,7 +111,6 @@ If STREAMING-P is non-nil, use the streaming endpoint." (let ((buf (current-buffer))) (llm-request-plz-async (llm-gemini--chat-url provider nil) :data (llm-gemini--chat-request prompt) - :media-type '(application/json) :on-success (lambda (data) (llm-request-callback-in-buffer buf response-callback @@ -131,12 +129,15 @@ If STREAMING-P is non-nil, use the streaming endpoint." (llm-gemini--chat-url provider t) :data (llm-gemini--chat-request prompt) :on-element (lambda (element) - (when-let ((response (llm-vertex--get-chat-response element))) - (if (stringp response) - (when (> (length response) 0) - (setq streamed-text (concat streamed-text response)) - (llm-request-callback-in-buffer buf partial-callback streamed-text)) - (setq function-call response)))) + (if (alist-get 'error element) + (llm-request-callback-in-buffer buf error-callback 'error + (llm-vertex--error-message element)) + (when-let ((response (llm-vertex--get-chat-response element))) + (if (stringp response) + (when (> (length response) 0) + (setq streamed-text (concat streamed-text response)) + (llm-request-callback-in-buffer buf partial-callback streamed-text)) + (setq function-call response))))) :on-success (lambda (data) (llm-request-callback-in-buffer buf response-callback diff --git a/llm-ollama.el b/llm-ollama.el index e36701ff25..0a80e9955f 100644 --- a/llm-ollama.el +++ b/llm-ollama.el @@ -86,7 +86,6 @@ PROVIDER is the llm-ollama provider." (let ((buf (current-buffer))) (llm-request-plz-async (llm-ollama--url provider "embeddings") :data (llm-ollama--embedding-request provider string) - :media-type '(application/json) :on-success (lambda (data) (llm-request-callback-in-buffer buf vector-callback (llm-ollama--embedding-extract-response data))) @@ -148,18 +147,16 @@ STREAMING is a boolean to control whether to stream the response." (llm-request-plz-async (llm-ollama--url provider "chat") :data (llm-ollama--chat-request provider prompt nil) - :media-type '(application/json) :timeout llm-ollama-chat-timeout :on-success (lambda (data) - (let ((output (llm-ollama--get-response data))) - (llm-provider-utils-append-to-prompt prompt data) - (llm-request-plz-callback-in-buffer buf response-callback output))) - :on-error (lambda (_ data) - (let ((errdata (cdr (assoc 'error data)))) + (let ((response (llm-ollama--get-response data))) + (llm-provider-utils-append-to-prompt prompt response) + (llm-request-plz-callback-in-buffer buf response-callback response))) + :on-error (lambda (code data) + (let ((error-message (cdr (assoc 'error data)))) (llm-request-plz-callback-in-buffer buf error-callback 'error (format "Problem calling Ollama: %s message: %s" - (cdr (assoc 'type errdata)) - (cdr (assoc 'message errdata))))))))) + code error-message))))))) (cl-defmethod llm-chat-streaming ((provider llm-ollama) prompt partial-callback response-callback error-callback) (let ((buf (current-buffer)) @@ -179,7 +176,7 @@ STREAMING is a boolean to control whether to stream the response." :on-error (lambda (_ _) ;; The problem with ollama is that it doesn't ;; seem to have an error response. - (llm-request-callback-in-buffer buf error-callback "Unknown error calling ollama"))))) + (llm-request-callback-in-buffer buf error-callback 'error "Unknown error calling ollama"))))) (cl-defmethod llm-name ((provider llm-ollama)) (llm-ollama-chat-model provider)) diff --git a/llm-openai.el b/llm-openai.el index 28396ce045..e444824262 100644 --- a/llm-openai.el +++ b/llm-openai.el @@ -116,7 +116,6 @@ This is just the key, if it exists." (llm-openai--check-key provider) (let ((buf (current-buffer))) (llm-request-plz-async (llm-openai--url provider "embeddings") - :media-type '(application/json) :headers (llm-openai--headers provider) :data (llm-openai--embedding-request (llm-openai-embedding-model provider) string) :on-success (lambda (data) @@ -229,7 +228,6 @@ PROMPT is the prompt that needs to be updated with the response." (let ((buf (current-buffer))) (llm-request-plz-async (llm-openai--url provider "chat/completions") - :media-type '(application/json) :headers (llm-openai--headers provider) :data (llm-openai--chat-request (llm-openai-chat-model provider) prompt) :on-success (lambda (data) diff --git a/llm-request-plz.el b/llm-request-plz.el index a9cf9f63e9..fa7a9e5284 100644 --- a/llm-request-plz.el +++ b/llm-request-plz.el @@ -27,7 +27,7 @@ (require 'rx) (require 'url-http) -(defcustom llm-request-plz-timeout 60 +(defcustom llm-request-plz-timeout (* 2 60) "The number of seconds to wait for a response from a HTTP server. Request timings are depending on the request. Requests that need @@ -101,19 +101,24 @@ code and the HTTP body of the error response. For Curl errors, ON-ERROR will be called with the exit code of the curl process and an error message." - (cond ((plz-error-response error) - (let ((response (plz-error-response error))) - (funcall on-error - (plz-response-status response) - (plz-response-body response)))) + (cond ((plz-media-type-filter-error-p error) + (let ((cause (plz-media-type-filter-error-cause error)) + (response (plz-error-response error))) + ;; TODO: What do we want to pass to callers here? + (funcall on-error 'filter-error cause))) ((plz-error-curl-error error) (let ((curl-error (plz-error-curl-error error))) (funcall on-error (car curl-error) (cdr curl-error)))) + ((plz-error-response error) + (when-let ((response (plz-error-response error)) + (status (plz-response-status response)) + (body (plz-response-body response))) + (funcall on-error status body))) (t (user-error "Unexpected error: %s" error)))) -(cl-defun llm-request-plz-async (url &key headers data on-success on-success-raw media-type +(cl-defun llm-request-plz-async (url &key headers data on-success media-type on-error timeout) "Make a request to URL. Nothing will be returned. @@ -131,30 +136,21 @@ and required otherwise. ON-ERROR will be called with the error code and a response-body. This is required. -MEDIA-TYPE is a required argument that sets a media type, useful -for streaming formats. It is expected that this is only used by -other methods in this file. - -ON-SUCCESS-RAW, if set, will be called in the buffer with the -response body, and expect the response content. This is an -optional argument, and mostly useful for streaming. If not set, -the buffer is turned into JSON and passed to ON-SUCCESS." - (unless media-type - (error "MEDIA-TYPE is required in llm-request-plz-async")) +MEDIA-TYPE is an optional argument that adds or overrides a media +type, useful for streaming formats. It is expected that this is +only used by other methods in this file." (plz-media-type-request 'post url - :as `(media-types ,(cons media-type plz-media-types)) + :as `(media-types ,(if media-type + (cons media-type plz-media-types) + plz-media-types)) :body (when data (encode-coding-string (json-encode data) 'utf-8)) :headers (append headers '(("Content-Type" . "application/json"))) :then (lambda (response) - (let ((response (plz-response-body response))) - (when on-success-raw - (funcall on-success-raw response)) - (when on-success - (funcall on-success (when (and response (> (length response) 0)) - (json-read-from-string response)))))) + (when on-success + (funcall on-success (plz-response-body response)))) :else (lambda (error) (when on-error (llm-request-plz--handle-error error on-error))) @@ -184,9 +180,7 @@ This is required. :headers headers :data data :on-error on-error - ;; Have to use :on-success-raw because :on-success will try to - ;; convert to JSON, and this already should be JSON. - :on-success-raw on-success + :on-success on-success :timeout timeout :media-type (cons 'application/json @@ -216,10 +210,7 @@ This is required. :headers headers :data data :on-error on-error - ;; Have to use :on-success-raw because :on-success will try to - ;; convert to JSON, and this already should be JSON. - :on-success-raw (lambda (resp) - (funcall on-success (plz-response-body resp))) + :on-success on-success :timeout timeout :media-type (cons 'application/x-ndjson diff --git a/llm-vertex.el b/llm-vertex.el index ec16a2462d..3ee7083f0e 100644 --- a/llm-vertex.el +++ b/llm-vertex.el @@ -130,7 +130,6 @@ KEY-GENTIME keeps track of when the key was generated, because the key must be r (llm-vertex--embedding-url provider) :headers `(("Authorization" . ,(format "Bearer %s" (llm-vertex-key provider)))) :data `(("instances" . [(("content" . ,string))])) - :media-type '(application/json) :on-success (lambda (data) (llm-request-callback-in-buffer buf vector-callback (llm-vertex--embedding-extract-response data))) @@ -302,7 +301,6 @@ If STREAMING is non-nil, use the URL for the streaming API." (llm-vertex--chat-url provider) :headers `(("Authorization" . ,(format "Bearer %s" (llm-vertex-key provider)))) :data (llm-vertex--chat-request prompt) - :media-type '(application/json) :on-success (lambda (data) (llm-request-callback-in-buffer buf response-callback @@ -322,12 +320,15 @@ If STREAMING is non-nil, use the URL for the streaming API." :headers `(("Authorization" . ,(format "Bearer %s" (llm-vertex-key provider)))) :data (llm-vertex--chat-request prompt) :on-element (lambda (element) - (when-let ((response (llm-vertex--get-chat-response element))) - (if (stringp response) - (when (> (length response) 0) - (setq streamed-text (concat streamed-text response)) - (llm-request-callback-in-buffer buf partial-callback streamed-text)) - (setq function-call response)))) + (if (alist-get 'error element) + (llm-request-callback-in-buffer buf error-callback 'error + (llm-vertex--error-message element)) + (when-let ((response (llm-vertex--get-chat-response element))) + (if (stringp response) + (when (> (length response) 0) + (setq streamed-text (concat streamed-text response)) + (llm-request-callback-in-buffer buf partial-callback streamed-text)) + (setq function-call response))))) :on-success (lambda (data) (llm-request-callback-in-buffer buf response-callback diff --git a/plz-event-source.el b/plz-event-source.el index c6861f7deb..b522cf0c1b 100644 --- a/plz-event-source.el +++ b/plz-event-source.el @@ -405,7 +405,16 @@ (events :documentation "Association list from event type to handler." :initarg :events :initform nil - :type list))) + :type list)) + "A media type class that handles the processing of HTTP responses +in the server sent events format. The HTTP response is processed +in a streaming way. The :events slot of the class can be set to +an association list from event type symbol to a handler function. +Whenever a new event is parsed and emitted the handler for the +corresponding event type will be called with two arguments, an +instance of the underlying event source class and an event. The +body slot of the plz-response struct passed to the THEN and ELSE +callbacks will always be set to nil.") (defvar-local plz-event-source--current nil "The event source of the current buffer.") diff --git a/plz-media-type.el b/plz-media-type.el index 23008e49c7..c20855487d 100644 --- a/plz-media-type.el +++ b/plz-media-type.el @@ -4,6 +4,10 @@ ;; Author: r0man <ro...@burningswell.com> ;; Maintainer: r0man <ro...@burningswell.com> +;; URL: https://github.com/r0man/plz-media-type.el +;; Version: 0.1-pre +;; Package-Requires: ((emacs "26.3")) +;; Keywords: comm, network, http ;; This file is part of GNU Emacs. @@ -24,7 +28,13 @@ ;;; Commentary: -;; This file handles content type. +;; This library provides enhanced handling of MIME types for HTTP +;; requests within Emacs. It utilizes the 'plz' library for +;; networking calls, extending it to process responses based on the +;; Content-Type header. This library defines various classes and +;; methods for parsing and processing standard MIME types, including +;; JSON, XML, HTML, and binary data. It allows for extensible +;; processing of additional types through subclassing. ;;; Code: @@ -34,6 +44,13 @@ (require 'eieio) (require 'plz) +(define-error 'plz-media-type-filter-error + "plz-media-type: Error in process filter" + 'plz-error) + +(cl-defstruct (plz-media-type-filter-error (:include plz-error)) + cause) + (defclass plz-media-type () ((type :documentation "The media type." @@ -47,7 +64,11 @@ :documentation "The parameters of the media type." :initarg :parameters :initform nil - :subtype list))) + :subtype list)) + "A class that hold information about the type, subtype and +parameters of a media type. It is meant to be sub-classed to +handle the processing of different media types and supports the +processing of streaming and non-streaming HTTP responses.") (defun plz-media-type-charset (media-type) "Return the character set of the MEDIA-TYPE." @@ -158,15 +179,18 @@ CHUNK is a part of the HTTP body." (media-type (plz-media-type--of-response media-types response)) (coding-system (plz-media-type-coding-system media-type))) (setq-local plz-media-type--current media-type) + (setq-local plz-media-type--response + (make-plz-response + :headers (plz-response-headers response) + :status (plz-response-status response) + :version (plz-response-version response))) (when-let (body (plz-response-body response)) (when (> (length body) 0) (setf (plz-response-body response) (decode-coding-string body coding-system)) (delete-region body-start (point-max)) (set-marker (process-mark process) (point)) - (plz-media-type-process media-type process response))) - (setf (plz-response-body response) nil) - (setq-local plz-media-type--response response)))))) + (plz-media-type-process media-type process response)))))))) (when moving (goto-char (process-mark process))))))) @@ -174,13 +198,17 @@ CHUNK is a part of the HTTP body." (defclass plz-media-type:application/octet-stream (plz-media-type) ((type :initform 'application) - (subtype :initform 'octet-stream))) + (subtype :initform 'octet-stream)) + "Media type class that handles the processing of octet stream +HTTP responses. The media type sets the body slot of the +plz-response structure to the unmodified value of the HTTP response +body. It is used as the default media type processor.") (cl-defmethod plz-media-type-else ((media-type plz-media-type:application/octet-stream) error) "Transform the ERROR into a format suitable for MEDIA-TYPE." - (let ((response (plz-error-response error))) - (setf (plz-error-response error) (plz-media-type-then media-type response)) - error)) + (when-let (response (plz-error-response error)) + (setf (plz-error-response error) (plz-media-type-then media-type response))) + error) (cl-defmethod plz-media-type-then ((media-type plz-media-type:application/octet-stream) response) "Transform the RESPONSE into a format suitable for MEDIA-TYPE." @@ -221,7 +249,15 @@ defaults to `nil`." be `hash-table', `alist' (the default) or `plist'." :initarg :object-type :initform 'alist - :type symbol))) + :type symbol)) + "Media type class that handles the processing of HTTP responses +in the JSON format. The HTTP response is processed in a +non-streaming way. After the response has been received, the +body of the plz-response structure is set to the result of parsing +the HTTP response body with the `json-parse-buffer' function. +The arguments to the `json-parse-buffer' can be customized by +making an instance of this class and setting its slots +accordingly.") (defun plz-media-type--parse-json-object (media-type) "Parse the JSON object in the current buffer according to MEDIA-TYPE." @@ -240,9 +276,17 @@ be `hash-table', `alist' (the default) or `plist'." (defclass plz-media-type:application/json-array (plz-media-type:application/json) ((handler - :documentation "A function that will be called for each object in the JSON array." + :documentation "Function that will be called for each object in the JSON array." :initarg :handler - :type (or function symbol)))) + :type (or function symbol))) + "Media type class that handles the processing of HTTP responses +in a JSON format that assumes that the object at the top level is +an array. The HTTP response is processed in a streaming way. +Each object in the top level array will be parsed with the +`json-parse-buffer' function. The function in the :handler slot +will be called each time a new object arrives. The body slot of +the plz-response structure passed to the THEN and ELSE callbacks +will always be set to nil.") (defun plz-media-type:application/json-array--parse-next (media-type) "Parse a single line of the newline delimited JSON MEDIA-TYPE." @@ -306,10 +350,18 @@ be `hash-table', `alist' (the default) or `plist'." (defclass plz-media-type:application/x-ndjson (plz-media-type:application/json) ((subtype :initform 'x-ndjson) (handler - :documentation "A function that will be called for each line that contains a JSON object." + :documentation "Function that will be called for each line that contains a JSON object." :initarg :handler :initform nil - :type (or function null symbol)))) + :type (or function null symbol))) + "Media type class that handles the processing of HTTP responses +in a JSON format that assumes that the object at the top level is +an array. The HTTP response is processed in a streaming way. +Each object in the top level array will be parsed with the +`json-parse-buffer' function. The function in the :handler slot +will be called each time a new object arrives. The body slot of +the plz-response structure passed to the THEN and ELSE callbacks +will always be set to nil.") (defconst plz-media-type:application/x-ndjson--line-regexp (rx (* not-newline) (or "\r\n" "\n" "\r")) @@ -342,24 +394,38 @@ be `hash-table', `alist' (the default) or `plist'." (cl-defmethod plz-media-type-then ((media-type plz-media-type:application/x-ndjson) response) "Transform the RESPONSE into a format suitable for MEDIA-TYPE." (plz-media-type:application/x-ndjson--parse-stream media-type) + (setf (plz-response-body response) nil) response) ;; Content Type: application/xml (defclass plz-media-type:application/xml (plz-media-type:application/octet-stream) - ((subtype :initform 'xml))) + ((subtype :initform 'xml)) + "Media type class that handles the processing of HTTP responses +in the XML format. The HTTP response is processed in a +non-streaming way. After the response has been received, the +body of the plz-response structure is set to the result of parsing +the HTTP response body with the `libxml-parse-html-region' +function.") (cl-defmethod plz-media-type-then ((media-type plz-media-type:application/xml) response) "Transform the RESPONSE into a format suitable for MEDIA-TYPE." (with-slots (array-type false-object null-object object-type) media-type - (setf (plz-response-body response) (libxml-parse-html-region)) + (setf (plz-response-body response) + (libxml-parse-html-region (point-min) (point-max) nil)) response)) ;; Content Type: text/html (defclass plz-media-type:text/html (plz-media-type:application/xml) ((type :initform 'text) - (subtype :initform 'xml))) + (subtype :initform 'xml)) + "Media type class that handles the processing of HTTP responses +in the HTML format. The HTTP response is processed in a +non-streaming way. After the response has been received, the +body of the plz-response structure is set to the result of parsing +the HTTP response body with the `libxml-parse-html-region' +function.") (defvar plz-media-types `((application/json . ,(plz-media-type:application/json)) @@ -367,7 +433,7 @@ be `hash-table', `alist' (the default) or `plist'." (application/xml . ,(plz-media-type:application/xml)) (text/html . ,(plz-media-type:text/html)) (t . ,(plz-media-type:application/octet-stream))) - "Alist from media type to content type.") + "Association list from media type to content type.") (defun plz-media-type--handle-sync-http-error (error media-types) "Handle the synchronous HTTP ERROR using MEDIA-TYPES." @@ -387,9 +453,15 @@ be `hash-table', `alist' (the default) or `plist'." (defun plz-media-type--handle-sync-error (error media-types) "Handle the synchronous ERROR using MEDIA-TYPES." - (if (eq 'plz-http-error (car error)) - (plz-media-type--handle-sync-http-error error media-types) - (signal (car error) (cdr error)))) + (cond + ((plz-media-type-filter-error-p error) + (signal 'plz-media-type-filter-error + (list (plz-media-type-filter-error-message error) + (plz-media-type-filter-error-response error) + (plz-media-type-filter-error-cause error)))) + ((eq 'plz-http-error (car error)) + (plz-media-type--handle-sync-http-error error media-types)) + (t (signal (car error) (cdr error))))) (cl-defun plz-media-type-request (method @@ -435,11 +507,26 @@ It may be: non-existent file; if it exists, it will not be overwritten, and an error will be signaled. -- `(stream :through PROCESS-FILTER)' to asynchronously stream the - HTTP response. PROCESS-FILTER is an Emacs process filter - function, and must accept two arguments: the curl process - sending the request and a chunk of the HTTP body, which was - just received. +- `(media-types MEDIA-TYPES)' to handle the processing of the + response based on the Content-Type header. MEDIA-TYPES is an + association list from a content type symbol to an instance of a + `plz-media-type' class. The `plz-media-types' variable is + bound to an association list and can be used to handle some + commonly used formats such as JSON, HTML, XML. This list can + be used as a basis and is meant to be extended by users. If no + media type was found for a content type, it will be handled by + the default octet stream media type. When this option is used, + the THEN callback will always receive a plz-response structure as + argument, and the ELSE callback always a plz-error structure. The + plz-response structure will always have the status and header + slots set. The body slot depends on the media type + implementation. In the case for JSON, HTML, XML it will + contain the decoded response body. When receiving JSON for + example, it will be an Emacs Lisp association list. For + streaming responses like text/event-stream it will be set to + nil, and the events of the server sent events specification + will be dispatched to the handlers registered with the media + type instance. - A function, which is called in the response buffer with it narrowed to the response body (suitable for, e.g. `json-read'). @@ -493,51 +580,64 @@ not. (if-let (media-types (pcase as (`(media-types ,media-types) media-types))) - (condition-case error - (let* ((buffer) - (plz-curl-default-args (cons "--no-buffer" plz-curl-default-args)) - (result (plz method url - :as 'buffer - :body body - :body-type body-type - :connect-timeout connect-timeout - :decode decode - :else (lambda (error) - (setq buffer (current-buffer)) - (when (or (functionp else) (symbolp else)) - (funcall else (plz-media-type-else - plz-media-type--current - error)))) - :finally (lambda () - (unwind-protect - (when (functionp finally) - (funcall finally)) - (when (buffer-live-p buffer) - (kill-buffer buffer)))) - :headers headers - :noquery noquery - :process-filter (lambda (process chunk) - (plz-media-type-process-filter process media-types chunk)) - :timeout timeout - :then (if (symbolp then) - then - (lambda (_) + (let ((buffer) (filter-error)) + (condition-case error + (let* ((plz-curl-default-args (cons "--no-buffer" plz-curl-default-args)) + (result (plz method url + :as 'buffer + :body body + :body-type body-type + :connect-timeout connect-timeout + :decode decode + :else (lambda (error) (setq buffer (current-buffer)) - (when (or (functionp then) (symbolp then)) - (funcall then (plz-media-type-then - plz-media-type--current - plz-media-type--response)))))))) - (cond ((bufferp result) - (unwind-protect - (with-current-buffer result - (plz-media-type-then plz-media-type--current plz-media-type--response)) - (when (buffer-live-p result) - (kill-buffer result)))) - ((processp result) - result) - (t (user-error "Unexpected response: %s" result)))) - ;; TODO: How to kill the buffer for sync requests that raise an error? - (plz-error (plz-media-type--handle-sync-error error media-types))) + (when (or (functionp else) (symbolp else)) + (funcall else (or filter-error + (plz-media-type-else + plz-media-type--current + error))))) + :finally (lambda () + (unwind-protect + (when (functionp finally) + (funcall finally)) + (when (buffer-live-p buffer) + (kill-buffer buffer)))) + :headers headers + :noquery noquery + :process-filter (lambda (process chunk) + (condition-case cause + (plz-media-type-process-filter process media-types chunk) + (error + (let ((buffer (process-buffer process))) + (setq filter-error + (make-plz-media-type-filter-error + :cause cause + :message (format "error in process filter: %S" cause) + :response (when (buffer-live-p buffer) + (with-current-buffer buffer + plz-media-type--response)))) + (delete-process process))))) + :timeout timeout + :then (if (symbolp then) + then + (lambda (_) + (setq buffer (current-buffer)) + (when (or (functionp then) (symbolp then)) + (funcall then (plz-media-type-then + plz-media-type--current + plz-media-type--response)))))))) + (cond ((bufferp result) + (unwind-protect + (with-current-buffer result + (plz-media-type-then plz-media-type--current plz-media-type--response)) + (when (buffer-live-p result) + (kill-buffer result)))) + ((processp result) + result) + (t (user-error "Unexpected response: %s" result)))) + ;; TODO: How to kill the buffer for sync requests that raise an error? + (plz-error + (plz-media-type--handle-sync-error (or filter-error error) media-types)))) (apply #'plz (append (list method url) rest)))) ;;;; Footer diff --git a/plz.el b/plz.el index 69072063c7..3a9271bca6 100644 --- a/plz.el +++ b/plz.el @@ -755,6 +755,7 @@ argument passed to `plz--sentinel', which see." (pcase-exhaustive status ((or 0 "finished\n") ;; Curl exited normally: check HTTP status code. + (widen) (goto-char (point-min)) (plz--skip-proxy-headers) (while (plz--skip-redirect-headers))