branch: elpa/gptel commit 9077a8b119a3a83606b3f684f9c06de13a291f1f Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com> Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
gptel: Extend save/restore state to support tools (#963) - Extend saved chat state to include gptel tools. Active tool names are written to and read from Org properties (in Org mode) and file-local variables (elsewhere). - Additionally, handle "deletions" correctly. That is, remove existing Org properties and file-local variables for settings that are set to nil. For some settings (like the backend/model) a nil value does not make sense and is ignored. The system message could be nil and this edge case is not yet handled. Other persisted settings are handled correctly. * gptel-org.el (gptel-org--send-with-props) (gptel-org--entry-properties, gptel-org--restore-state) (gptel-org-set-properties): Handle the GPTEL_TOOLS property. * gptel.el (gptel--tool-names, gptel--backend-name) (gptel--restore-state, gptel--save-state): Use a new variable `gptel--tool-names' to persist gptel tool names for the chat session. --- gptel-org.el | 53 ++++++++++++++++++++++++++++++++++++----------------- gptel.el | 42 +++++++++++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/gptel-org.el b/gptel-org.el index 5269b5eb4e8..f9a6b17c768 100644 --- a/gptel-org.el +++ b/gptel-org.el @@ -424,11 +424,13 @@ parameters. ARGS are the original function call arguments." (if (derived-mode-p 'org-mode) (pcase-let ((`(,gptel--system-message ,gptel-backend ,gptel-model - ,gptel-temperature ,gptel-max-tokens) + ,gptel-temperature ,gptel-max-tokens + ,gptel--num-messages-to-send ,gptel-tools) (seq-mapn (lambda (a b) (or a b)) (gptel-org--entry-properties) (list gptel--system-message gptel-backend gptel-model - gptel-temperature gptel-max-tokens)))) + gptel-temperature gptel-max-tokens + gptel--num-messages-to-send gptel-tools)))) (apply send-fun args)) (apply send-fun args))) @@ -445,12 +447,12 @@ ARGS are the original function call arguments." (defun gptel-org--entry-properties (&optional pt) "Find gptel configuration properties stored at PT." (pcase-let - ((`(,system ,backend ,model ,temperature ,tokens ,num) + ((`(,system ,backend ,model ,temperature ,tokens ,num ,tools) (mapcar (lambda (prop) (org-entry-get (or pt (point)) prop 'selective)) '("GPTEL_SYSTEM" "GPTEL_BACKEND" "GPTEL_MODEL" "GPTEL_TEMPERATURE" "GPTEL_MAX_TOKENS" - "GPTEL_NUM_MESSAGES_TO_SEND")))) + "GPTEL_NUM_MESSAGES_TO_SEND" "GPTEL_TOOLS")))) (when system (setq system (string-replace "\\n" "\n" system))) (when backend @@ -461,7 +463,16 @@ ARGS are the original function call arguments." (setq temperature (gptel--to-number temperature))) (when tokens (setq tokens (gptel--to-number tokens))) (when num (setq num (gptel--to-number num))) - (list system backend model temperature tokens num))) + (when tools + (setq tools (cl-loop + for tname in (split-string tools) + for tool = (with-demoted-errors "gptel: %S" + (gptel-get-tool tname)) + if tool collect tool else do + (display-warning + '(gptel org tools) + (format "Tool %s not found, ignoring" tname))))) + (list system backend model temperature tokens num tools))) (defun gptel-org--restore-state () "Restore gptel state for Org buffers when turning on `gptel-mode'." @@ -472,7 +483,7 @@ ARGS are the original function call arguments." (progn (when-let* ((bounds (org-entry-get (point-min) "GPTEL_BOUNDS"))) (gptel--restore-props (read bounds))) - (pcase-let ((`(,system ,backend ,model ,temperature ,tokens ,num) + (pcase-let ((`(,system ,backend ,model ,temperature ,tokens ,num ,tools) (gptel-org--entry-properties (point-min)))) (when system (setq-local gptel--system-message system)) (if backend (setq-local gptel-backend backend) @@ -486,7 +497,8 @@ ARGS are the original function call arguments." (when model (setq-local gptel-model model)) (when temperature (setq-local gptel-temperature temperature)) (when tokens (setq-local gptel-max-tokens tokens)) - (when num (setq-local gptel--num-messages-to-send num)))) + (when num (setq-local gptel--num-messages-to-send num)) + (when tools (setq-local gptel-tools tools)))) (:success (message "gptel chat restored.")) (error (message "Could not restore gptel state, sorry! Error: %s" status))) (set-buffer-modified-p modified)))) @@ -503,20 +515,27 @@ non-nil (default), display a message afterwards." (interactive (list (point) t)) (org-entry-put pt "GPTEL_MODEL" (gptel--model-name gptel-model)) (org-entry-put pt "GPTEL_BACKEND" (gptel-backend-name gptel-backend)) - (unless (equal (default-value 'gptel-temperature) gptel-temperature) - (org-entry-put pt "GPTEL_TEMPERATURE" - (number-to-string gptel-temperature))) - (when (natnump gptel--num-messages-to-send) - (org-entry-put pt "GPTEL_NUM_MESSAGES_TO_SEND" - (number-to-string gptel--num-messages-to-send))) - (org-entry-put pt "GPTEL_SYSTEM" + (org-entry-put pt "GPTEL_SYSTEM" ;TODO: Handle nil case correctly (and-let* ((msg (car-safe (gptel--parse-directive gptel--system-message)))) (string-replace "\n" "\\n" msg))) - (when gptel-max-tokens - (org-entry-put - pt "GPTEL_MAX_TOKENS" (number-to-string gptel-max-tokens))) + (if gptel-tools + (org-entry-put + pt "GPTEL_TOOLS" (mapconcat #'gptel-tool-name gptel-tools " ")) + (org-entry-delete pt "GPTEL_TOOLS")) + (if (equal (default-value 'gptel-temperature) gptel-temperature) + (org-entry-delete pt "GPTEL_TEMPERATURE") + (org-entry-put pt "GPTEL_TEMPERATURE" + (number-to-string gptel-temperature))) + (if (natnump gptel--num-messages-to-send) + (org-entry-put pt "GPTEL_NUM_MESSAGES_TO_SEND" + (number-to-string gptel--num-messages-to-send)) + (org-entry-delete pt "GPTEL_NUM_MESSAGES_TO_SEND")) + (if gptel-max-tokens + (org-entry-put + pt "GPTEL_MAX_TOKENS" (number-to-string gptel-max-tokens)) + (org-entry-delete pt "GPTEL_MAX_TOKENS")) (when msg (message "Added gptel configuration to current headline."))) diff --git a/gptel.el b/gptel.el index 02c0625dd95..f67b9d3255e 100644 --- a/gptel.el +++ b/gptel.el @@ -802,6 +802,13 @@ This opens up advanced options in `gptel-menu'.") (defvar gptel--num-messages-to-send nil) (put 'gptel--num-messages-to-send 'safe-local-variable #'always) +(defvar-local gptel--tool-names nil + "Store to persist tool names to file across Emacs sessions. + +Note: Changing this variable does not affect gptel\\='s behavior +in any way.") +(put 'gptel--backend-name 'safe-local-variable #'always) + (defcustom gptel-log-level nil "Logging level for gptel. @@ -1429,7 +1436,17 @@ applied before being re-persisted in the new structure." "Could not activate gptel backend \"%s\"! " "Switch backends with \\[universal-argument] \\[gptel-send]" " before using gptel.")) - gptel--backend-name)))))) + gptel--backend-name))) + (when gptel--tool-names + (if-let* ((tools (cl-loop + for tname in gptel--tool-names + for tool = (with-demoted-errors "gptel: %S" + (gptel-get-tool tname)) + if tool collect tool else do + (display-warning + '(gptel org tools) + (format "Tool %s not found, ignoring" tname))))) + (setq-local gptel-tools tools)))))) (defun gptel--save-state () "Write the gptel state to the buffer. @@ -1448,18 +1465,25 @@ file." (add-file-local-variable 'gptel-model gptel-model) (add-file-local-variable 'gptel--backend-name (gptel-backend-name gptel-backend)) - (unless (equal (default-value 'gptel-temperature) gptel-temperature) - (add-file-local-variable 'gptel-temperature gptel-temperature)) (unless (equal (default-value 'gptel--system-message) gptel--system-message) (add-file-local-variable - 'gptel--system-message + 'gptel--system-message ;TODO: Handle nil case correctly (car-safe (gptel--parse-directive gptel--system-message)))) - (when gptel-max-tokens - (add-file-local-variable 'gptel-max-tokens gptel-max-tokens)) - (when (natnump gptel--num-messages-to-send) - (add-file-local-variable 'gptel--num-messages-to-send - gptel--num-messages-to-send)) + (if gptel-tools + (add-file-local-variable + 'gptel--tool-names (mapcar #'gptel-tool-name gptel-tools)) + (delete-file-local-variable 'gptel--tool-names)) + (if (equal (default-value 'gptel-temperature) gptel-temperature) + (delete-file-local-variable 'gptel-temperature) + (add-file-local-variable 'gptel-temperature gptel-temperature)) + (if gptel-max-tokens + (add-file-local-variable 'gptel-max-tokens gptel-max-tokens) + (delete-file-local-variable 'gptel-max-tokens)) + (if (natnump gptel--num-messages-to-send) + (add-file-local-variable 'gptel--num-messages-to-send + gptel--num-messages-to-send) + (delete-file-local-variable 'gptel--num-messages-to-send)) (add-file-local-variable 'gptel--bounds (gptel--get-buffer-bounds)))))))