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)))))))
 
 

Reply via email to