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