.emacs.d

the most bloated Emacs config on this planet
git clone https://git.trogloxene.org/.emacs.d.git
Log | Files | Refs

init-gptel.el (8883B)


      1 ;;; init-gptel.el --- ai -*- lexical-binding: t -*-
      2 
      3 ;;; Commentary:
      4 ;; I vibe coded this entire part.
      5 
      6 ;;; Code:
      7 
      8 (use-package gptel
      9   :config
     10   (setq gptel-log-level t
     11         gptel-default-mode 'org-mode
     12         gptel-include-reasoning "*gptel-reasoning*")
     13   (add-hook 'gptel-post-response-functions 'gptel-end-of-response)
     14   (add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
     15   (setq gptel-prompt-prefix-alist '((markdown-mode . "## User: ") (org-mode . "** User: ") (text-mode . "## User: ")))
     16 
     17   (gptel-make-tool
     18    :name "read_buffer"                    ; javascript-style snake_case name
     19    :function (lambda (buffer)                  ; the function that will run
     20                (unless (buffer-live-p (get-buffer buffer))
     21                  (error "error: buffer %s is not live." buffer))
     22                (with-current-buffer  buffer
     23                  (buffer-substring-no-properties (point-min) (point-max))))
     24    :description "return the contents of an emacs buffer"
     25    :args (list '(:name "buffer"
     26                        :type string            ; :type value must be a symbol
     27                        :description "the name of the buffer whose contents are to be retrieved"))
     28    :category "emacs")                     ; An arbitrary label for grouping
     29 
     30   (gptel-make-openai "gptel-Kimi-Code"
     31     :host "api.kimi.com"
     32     :endpoint "/coding/v1/chat/completions"
     33     :protocol "https"
     34     :key (lambda ()
     35            (auth-info-password (car (auth-source-search :host "api.kimi.com" :user "apikey"))))
     36     :stream t
     37     :models '(kimi-k3)
     38     :header (lambda (_)
     39               (when-let* ((key (gptel--get-api-key)))
     40                 `(("User-Agent" . "RooCode/3.30.3")
     41                   ("HTTP-Referer" . "https://github.com/RooVetGit/Roo-Cline")
     42                   ("X-Title" . "Roo Code")
     43                   ("Authorization" . ,(concat "Bearer " key))))))
     44 
     45   (setq gptel-model 'kimi-k3
     46         gptel-backend (gptel-get-backend "gptel-Kimi-Code"))
     47 
     48   (defun my-gptel--sanitize-filename (name)
     49     "Clean NAME into a safe kebab-case filename string."
     50     (let ((clean (downcase name)))
     51       (setq clean (replace-regexp-in-string "[^a-z0-9_-]" "-" clean))
     52       (setq clean (replace-regexp-in-string "^-+\\|[-]+$" "" clean))
     53       (setq clean (replace-regexp-in-string "-[-]+" "-" clean))
     54       (string-trim clean)))
     55 
     56   (defun my-gptel-save-session ()
     57     "Save current gptel session to ~/gptel/sessions/ with AI-generated
     58 filename.
     59 The filename is prefixed with a YYYYMMDD-HHmmss timestamp and generated
     60 from the last 1500 characters of the buffer."
     61     (interactive)
     62     (unless gptel-mode
     63       (user-error "Not a gptel session buffer"))
     64     (let* ((buf (current-buffer))
     65            (content (with-current-buffer buf
     66                       (buffer-substring-no-properties (point-min) (point-max))))
     67            (tail (substring content (max 0 (- (length content) 1500))))
     68            (prompt-text (format "Based on the following conversation, generate a short kebab-case
     69 filename (3 to 5 lowercase English words, hyphen-separated, no file
     70 extension). Output ONLY the filename, nothing else.\n\n%s"
     71                                 tail)))
     72       (message "Generating filename with AI...")
     73       (let ((fsm (gptel-request prompt-text
     74                    :system "You are a filename generator. Output ONLY a short kebab-case
     75 filename (3-5 lowercase English words separated by hyphens). No
     76 explanations, no quotes, no punctuation, no file extension."
     77                    :stream nil
     78                    :callback
     79                    (lambda (response info)
     80                      (condition-case err
     81                          (cond
     82                           ((null response)
     83                            (message "Failed to generate filename: %s"
     84                                     (or (plist-get info :status) "unknown error")))
     85                           ((not (stringp response))
     86                            (message "Unexpected response type: %S" response))
     87                           (t
     88                            (let* ((generated (my-gptel--sanitize-filename (string-trim response)))
     89                                   (generated (if (string-empty-p generated)
     90                                                  "gptel-session"
     91                                                generated))
     92                                   (timestamp (format-time-string "%Y%m%dT%H%M%S"))
     93                                   (dir (expand-file-name "~/gptel/sessions/"))
     94                                   (base (concat timestamp "--" generated))
     95                                   (file (expand-file-name (concat base ".org") dir)))
     96                              ;; handle name collision
     97                              (when (file-exists-p file)
     98                                (setq base (concat timestamp "-" generated "-" (format-time-string "%S")))
     99                                (setq file (expand-file-name (concat base ".org") dir)))
    100                              (unless (file-directory-p dir)
    101                                (make-directory dir t))
    102                              ;; write-file triggers gptel--save-state to preserve metadata
    103                              (with-current-buffer buf
    104                                (write-file file))
    105                              (if (file-exists-p file)
    106                                  (progn
    107                                    (pop-to-buffer (find-file-noselect file))
    108                                    (message "Saved gptel session to %s" file))
    109                                (message "ERROR: File was not created: %s" file)))))
    110                        (error (message "gptel save-session error: %S" err)))))))
    111         (unless (plist-get (gptel-fsm-info fsm) :callback)
    112           (message "WARNING: gptel-request did not register callback!")))))
    113 
    114   (with-eval-after-load 'gptel
    115     (define-key gptel-mode-map (kbd "C-c C-s") #'my-gptel-save-session)))
    116 
    117 (use-package gptel-agent
    118   :after gptel
    119   :demand t)
    120 
    121 (use-package gptel-magit
    122   :straight
    123   (gptel-magit :fork (:host github :repo "ArthurHeymans/gptel-magit"))
    124   :hook
    125   (magit-mode . gptel-magit-install)
    126   :init
    127   (with-eval-after-load 'gptel
    128     (gptel-make-openai "gptel-Kimi-Code-magit"
    129       :host "api.kimi.com"
    130       :endpoint "/coding/v1/chat/completions"
    131       :protocol "https"
    132       :key (lambda ()
    133              (auth-info-password (car (auth-source-search :host "api.kimi.com" :user "apikey"))))
    134       :stream t
    135       :models '((kimi-for-coding))
    136       :request-params '(:temperature 0.6 :thinking (:type "disabled"))
    137       :header (lambda (_)
    138                 (when-let* ((key (gptel--get-api-key)))
    139                   `(("User-Agent" . "RooCode/3.30.3")
    140                     ("HTTP-Referer" . "https://github.com/RooVetGit/Roo-Cline")
    141                     ("X-Title" . "Roo Code")
    142                     ("Authorization" . ,(concat "Bearer " key))))))
    143 
    144     (setq gptel-magit-model 'kimi-for-coding
    145           gptel-magit-backend (gptel-get-backend "gptel-Kimi-Code-magit"))))
    146 
    147 ;; pi
    148 (use-package pi-coding-agent
    149   :init
    150   (defun my-pi-coding-agent ()
    151     "Run `pi-coding-agent' in project root."
    152     (interactive)
    153     (when-let* ((proj (project-current t))
    154                 (default-directory (project-root proj)))
    155       (pi-coding-agent)))
    156 
    157   (setq pi-coding-agent-thinking-display 'hidden)
    158 
    159   :config
    160   (defun pi-coding-agent-session-browser-delete ()
    161     "Delete the session file at point, closing any buffers that hold it open."
    162     (interactive)
    163     (let ((section (magit-current-section)))
    164       (if (not (and section (eq (oref section type) 'session)))
    165           (message "Pi: No session at point")
    166         (let* ((path (oref section value))
    167                (holders
    168                 (cl-remove-if-not
    169                  (lambda (buf)
    170                    (and (buffer-live-p buf)
    171                         (with-current-buffer buf
    172                           (and (derived-mode-p 'pi-coding-agent-chat-mode)
    173                                (pi-coding-agent--browse-session-file-matches-p
    174                                 buf path)))))
    175                  (buffer-list))))
    176           (when (y-or-n-p (if holders
    177                               (format "Delete session and close %d buffer(s)? %s "
    178                                       (length holders) path)
    179                             (format "Delete session? %s " path)))
    180             (let ((pi-coding-agent-quit-without-confirmation t))
    181               (dolist (buf holders)
    182                 (kill-buffer buf)))
    183             (delete-file path t)
    184             (pi-coding-agent--session-browser-fetch-and-render)
    185             (message "Pi: Deleted %s" path))))))
    186 
    187   (keymap-set pi-coding-agent-session-browser-mode-map
    188               "D" #'pi-coding-agent-session-browser-delete)
    189 
    190   (transient-insert-suffix 'pi-coding-agent-session-browser-dispatch '(0 0 -1)
    191     '("D" "delete" pi-coding-agent-session-browser-delete)))
    192 
    193 (provide 'init-gptel)
    194 ;;; init-gptel.el ends here