- Prefix gptel backend names with "gptel-" for consistency - Add denote-org-backlinks-for-heading keybinding - Refine recentf exclusions for org journal files - Remove org-default-priority setting - Add gptel and denote backlink buffer rules to ibuffer - Enable markdown-ts-mode for Emacs 31+ with truncate lines - Add electric-pair support for equals sign - Update popper and window management regexes for new buffer names
149 lines
6.8 KiB
EmacsLisp
149 lines
6.8 KiB
EmacsLisp
;;; init-gptel.el --- ai -*- lexical-binding: t -*-
|
|
|
|
;;; Commentary:
|
|
|
|
;;; Code:
|
|
|
|
(use-package gptel
|
|
:hook
|
|
(gptel-mode-hook . visual-line-mode)
|
|
|
|
:config
|
|
(setq gptel-log-level t)
|
|
(setq gptel-default-mode 'org-mode)
|
|
(add-hook 'gptel-post-response-functions 'gptel-end-of-response)
|
|
(add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
|
|
(setq gptel-prompt-prefix-alist '((markdown-mode . "## User: ") (org-mode . "** User: ") (text-mode . "## User: ")))
|
|
|
|
(gptel-make-tool
|
|
:name "read_buffer" ; javascript-style snake_case name
|
|
:function (lambda (buffer) ; the function that will run
|
|
(unless (buffer-live-p (get-buffer buffer))
|
|
(error "error: buffer %s is not live." buffer))
|
|
(with-current-buffer buffer
|
|
(buffer-substring-no-properties (point-min) (point-max))))
|
|
:description "return the contents of an emacs buffer"
|
|
:args (list '(:name "buffer"
|
|
:type string ; :type value must be a symbol
|
|
:description "the name of the buffer whose contents are to be retrieved"))
|
|
:category "emacs") ; An arbitrary label for grouping
|
|
|
|
(gptel-make-openai "gptel-Kimi-Code"
|
|
:host "api.kimi.com"
|
|
:endpoint "/coding/v1/chat/completions"
|
|
:protocol "https"
|
|
:key (lambda ()
|
|
(auth-source-pick-first-password :host "api.kimi.com" :user "apikey"))
|
|
:stream t
|
|
:models '(kimi-for-coding)
|
|
:header (lambda (_)
|
|
(when-let ((key (gptel--get-api-key)))
|
|
`(("User-Agent" . "RooCode/3.30.3")
|
|
("HTTP-Referer" . "https://github.com/RooVetGit/Roo-Cline")
|
|
("X-Title" . "Roo Code")
|
|
("Authorization" . ,(concat "Bearer " key))))))
|
|
|
|
(setq gptel-model 'kimi-for-coding
|
|
gptel-backend (gptel-get-backend "gptel-Kimi-Code"))
|
|
|
|
(defun my-gptel--sanitize-filename (name)
|
|
"Clean NAME into a safe kebab-case filename string."
|
|
(let ((clean (downcase name)))
|
|
(setq clean (replace-regexp-in-string "[^a-z0-9_-]" "-" clean))
|
|
(setq clean (replace-regexp-in-string "^-+\\|[-]+$" "" clean))
|
|
(setq clean (replace-regexp-in-string "-[-]+" "-" clean))
|
|
(string-trim clean)))
|
|
|
|
(defun my-gptel-save-session ()
|
|
"Save current gptel session to ~/gptel/sessions/ with AI-generated filename.
|
|
The filename is prefixed with a YYYYMMDD-HHmmss timestamp and generated
|
|
from the last 1500 characters of the buffer."
|
|
(interactive)
|
|
(unless gptel-mode
|
|
(user-error "Not a gptel session buffer"))
|
|
(let* ((buf (current-buffer))
|
|
(content (with-current-buffer buf
|
|
(buffer-substring-no-properties (point-min) (point-max))))
|
|
(tail (substring content (max 0 (- (length content) 1500))))
|
|
(prompt-text (format "Based on the following conversation, generate a short kebab-case filename (3 to 5 lowercase English words, hyphen-separated, no file extension). Output ONLY the filename, nothing else.\n\n%s"
|
|
tail)))
|
|
(message "Generating filename with AI...")
|
|
(let ((fsm (gptel-request prompt-text
|
|
:system "You are a filename generator. Output ONLY a short kebab-case filename (3-5 lowercase English words separated by hyphens). No explanations, no quotes, no punctuation, no file extension."
|
|
:stream nil
|
|
:callback
|
|
(lambda (response info)
|
|
(condition-case err
|
|
(cond
|
|
((null response)
|
|
(message "Failed to generate filename: %s"
|
|
(or (plist-get info :status) "unknown error")))
|
|
((not (stringp response))
|
|
(message "Unexpected response type: %S" response))
|
|
(t
|
|
(let* ((generated (my-gptel--sanitize-filename (string-trim response)))
|
|
(generated (if (string-empty-p generated)
|
|
"gptel-session"
|
|
generated))
|
|
(timestamp (format-time-string "%Y%m%d-%H%M%S"))
|
|
(dir (expand-file-name "~/gptel/sessions/"))
|
|
(base (concat timestamp "-" generated))
|
|
(file (expand-file-name (concat base ".org") dir)))
|
|
;; handle name collision
|
|
(when (file-exists-p file)
|
|
(setq base (concat timestamp "-" generated "-" (format-time-string "%S")))
|
|
(setq file (expand-file-name (concat base ".org") dir)))
|
|
(unless (file-directory-p dir)
|
|
(make-directory dir t))
|
|
;; write-file triggers gptel--save-state to preserve metadata
|
|
(with-current-buffer buf
|
|
(write-file file))
|
|
(if (file-exists-p file)
|
|
(progn
|
|
(pop-to-buffer (find-file-noselect file))
|
|
(message "Saved gptel session to %s" file))
|
|
(message "ERROR: File was not created: %s" file)))))
|
|
(error (message "gptel save-session error: %S" err)))))))
|
|
(unless (plist-get (gptel-fsm-info fsm) :callback)
|
|
(message "WARNING: gptel-request did not register callback!")))))
|
|
|
|
(with-eval-after-load 'gptel
|
|
(define-key gptel-mode-map (kbd "C-c C-s") #'my-gptel-save-session)))
|
|
|
|
(use-package gptel-agent
|
|
:init
|
|
(with-eval-after-load 'gptel
|
|
(require 'gptel-agent))
|
|
:config
|
|
(add-to-list 'gptel-agent-dirs "~/.emacs.d/lisp/" t)
|
|
(gptel-agent-update))
|
|
|
|
(use-package gptel-magit
|
|
:hook
|
|
(magit-mode . gptel-magit-install)
|
|
|
|
:init
|
|
(with-eval-after-load 'gptel
|
|
(gptel-make-openai "gptel-Kimi-Code-magit"
|
|
:host "api.kimi.com"
|
|
:endpoint "/coding/v1/chat/completions"
|
|
:protocol "https"
|
|
:key (lambda ()
|
|
(auth-source-pick-first-password :host "api.kimi.com" :user "apikey"))
|
|
:stream t
|
|
:models '((kimi-for-coding))
|
|
:request-params '(:thinking (:type "disabled"))
|
|
:header (lambda (_)
|
|
(when-let ((key (gptel--get-api-key)))
|
|
`(("User-Agent" . "RooCode/3.30.3")
|
|
("HTTP-Referer" . "https://github.com/RooVetGit/Roo-Cline")
|
|
("X-Title" . "Roo Code")
|
|
("Authorization" . ,(concat "Bearer " key))))))
|
|
|
|
(setq gptel-magit-model 'kimi-for-coding
|
|
gptel-magit-backend (gptel-get-backend "gptel-Kimi-Code-magit"))))
|
|
|
|
(provide 'init-gptel)
|
|
|
|
;;; init-gptel.el ends here
|