move stuff around

This commit is contained in:
2026-04-27 18:57:10 -04:00
parent e57257a3a5
commit 4c6caec1d7
6 changed files with 665 additions and 74 deletions

346
user-lisp/llm-tools.el Normal file
View File

@@ -0,0 +1,346 @@
;;; llm-tools.el --- Emacs tools for GPTel models -*- lexical-binding: t -*-
;; Copyright (C) 2024
;; Author: Your Name
;; Keywords: tools, convenience, gptel
;; This file is NOT part of GNU Emacs.
;;; Commentary:
;; This file provides GPTel tools that allow LLM models to look up
;; information in Emacs, including function names and documentation,
;; variable names and documentation, key bindings, and more.
;;; Code:
(require 'gptel)
;; ============================================================================
;; Documentation Lookup Tools
;; ============================================================================
(gptel-make-tool
:name "get_function_doc"
:category "emacs"
:description "Get the documentation string for an Emacs Lisp function. Returns the function's docstring, argument list, and source file location."
:function (lambda (function-name)
(let ((sym (intern-soft function-name)))
(if (and sym (fboundp sym))
(let* ((doc (documentation sym t))
(arglist (help-function-arglist sym))
(file (find-lisp-object-file-name sym 'defun)))
(format "Function: %s\nArguments: %s\nFile: %s\n\nDocumentation:\n%s"
sym
(if arglist (prin1-to-string arglist) "(unknown)")
(or file "built-in/unknown")
(or doc "No documentation available")))
(format "Error: Function '%s' not found" function-name))))
:args '((:name "function-name"
:type string
:description "The name of the function (symbol) to look up"
:optional nil)))
(gptel-make-tool
:name "get_variable_doc"
:category "emacs"
:description "Get the documentation string for an Emacs Lisp variable. Returns the variable's docstring, current value (if safe to display), and source file location."
:function (lambda (variable-name)
(let ((sym (intern-soft variable-name)))
(if (and sym (boundp sym))
(let* ((doc (documentation-property sym 'variable-documentation))
(file (find-lisp-object-file-name sym 'defvar))
(value (bound-and-true-p sym))
(value-str (if (and value (or (numberp value)
(stringp value)
(symbolp value)
(and (listp value)
(< (length (format "%s" value)) 200))))
(format "%S" value)
"(complex value omitted)")))
(format "Variable: %s\nCurrent Value: %s\nFile: %s\n\nDocumentation:\n%s"
sym
value-str
(or file "built-in/unknown")
(or doc "No documentation available")))
(format "Error: Variable '%s' not found" variable-name))))
:args '((:name "variable-name"
:type string
:description "The name of the variable (symbol) to look up"
:optional nil)))
(gptel-make-tool
:name "get_face_doc"
:category "emacs"
:description "Get information about an Emacs face including its documentation and attributes."
:function (lambda (face-name)
(let ((sym (intern-soft face-name)))
(if (and sym (facep sym))
(let* ((doc (documentation-property sym 'face-documentation))
(attrs (face-all-attributes sym (selected-frame))))
(format "Face: %s\n\nDocumentation:\n%s\n\nAttributes:\n%s"
sym
(or doc "No documentation available")
(mapconcat (lambda (attr)
(format " %s: %S" (car attr) (cdr attr)))
attrs
"\n")))
(format "Error: Face '%s' not found" face-name))))
:args '((:name "face-name"
:type string
:description "The name of the face to look up"
:optional nil)))
(gptel-make-tool
:name "describe_key"
:category "emacs"
:description "Get the command bound to a specific key sequence in Emacs."
:function (lambda (key-sequence)
(condition-case err
(let* ((keys (read-kbd-macro key-sequence))
(binding (key-binding keys))
(cmd (if (arrayp binding)
(format "Keymap: %S" binding)
binding)))
(if (and cmd (not (eq cmd 'nil)))
(format "Key: %s\nBound to: %s\n\nDocumentation:\n%s"
key-sequence
(if (symbolp cmd) cmd (format "%S" cmd))
(if (symbolp cmd)
(or (documentation cmd t) "No documentation")
"Not a command"))
(format "Key '%s' is not bound to any command" key-sequence)))
(error (format "Error parsing key sequence '%s': %s"
key-sequence
(error-message-string err)))))
:args '((:name "key-sequence"
:type string
:description "The key sequence as a string (e.g., 'C-x C-f')"
:optional nil)))
(gptel-make-tool
:name "apropos_lookup"
:category "emacs"
:description "Look up Emacs symbols using apropos. Searches for symbols matching a pattern and returns their names, types, and documentation snippets."
:function (lambda (pattern type)
(let* ((symbols (apropos-internal pattern (lambda (s) (fboundp s))))
(vars (apropos-internal pattern (lambda (s) (boundp s))))
(faces (apropos-internal pattern (lambda (s) (facep s))))
(filter-fn (cond
((string= type "functions") (lambda (s) (memq s symbols)))
((string= type "variables") (lambda (s) (memq s vars)))
((string= type "faces") (lambda (s) (memq s faces)))
(t (lambda (s) t))))
(all-symbols (delete-dups (append symbols vars faces)))
(filtered (seq-filter filter-fn all-symbols)))
(if filtered
(format "Found %d symbols matching '%s':\n\n%s"
(length filtered)
pattern
(mapconcat (lambda (sym)
(format "%s (%s)\n %s"
sym
(cond ((fboundp sym) "function")
((boundp sym) "variable")
((facep sym) "face")
(t "symbol"))
(let ((doc (or (documentation sym t)
(documentation-property sym 'variable-documentation)
(documentation-property sym 'face-documentation)
"No documentation")))
(if (> (length doc) 100)
(concat (substring doc 0 100) "...")
doc))))
filtered
"\n\n"))
(format "No symbols matching pattern '%s' found" pattern))))
:args '((:name "pattern"
:type string
:description "The regex pattern to search for in symbol names"
:optional nil)
(:name "type"
:type string
:description "Filter by type: 'functions', 'variables', 'faces', or 'all' (default 'all')"
:optional t
:default "all")))
;; ============================================================================
;; Buffer/List Tools
;; ============================================================================
(gptel-make-tool
:name "list_modes"
:category "emacs"
:description "List major and minor modes available in Emacs."
:function (lambda (mode-type pattern)
(let ((major-modes (apropos-internal "-mode\\'" (lambda (s)
(and (boundp s)
(get s 'derived-mode-parent)))))
(minor-modes (apropos-internal "-mode\\'" (lambda (s)
(and (boundp s)
(get s 'minor-mode-function)))))
(filter (when pattern
(lambda (s) (string-match-p pattern (symbol-name s))))))
(when filter
(setq major-modes (seq-filter filter major-modes))
(setq minor-modes (seq-filter filter minor-modes)))
(let ((results
(cond
((string= mode-type "major")
(cons "Major Modes" major-modes))
((string= mode-type "minor")
(cons "Minor Modes" minor-modes))
(t
`(("Major Modes" . ,major-modes)
("Minor Modes" . ,minor-modes))))))
(if (consp (car results))
(format "%s\n\n%s"
(format "%s (%d):\n%s"
(caar results)
(length (cdar results))
(mapconcat (lambda (m) (format " - %s" m))
(cdar results)
"\n"))
(format "%s (%d):\n%s"
(cadr results)
(length (cddr results))
(mapconcat (lambda (m) (format " - %s" m))
(cddr results)
"\n")))
(format "%s (%d):\n%s"
(car results)
(length (cdr results))
(mapconcat (lambda (m) (format " - %s" m))
(cdr results)
"\n"))))))
:args '((:name "mode-type"
:type string
:description "Type of mode: 'major', 'minor', or 'all' (default 'all')"
:optional t
:default "all")
(:name "pattern"
:type string
:description "Optional regex pattern to filter mode names"
:optional t)))
;; ============================================================================
;; Source Location Tools
;; ============================================================================
(gptel-make-tool
:name "find_function_source"
:category "emacs"
:description "Find the file where an Emacs Lisp function is defined. Returns the file path and line number if available."
:function (lambda (function-name)
(let ((sym (intern-soft function-name)))
(if (and sym (fboundp sym))
(let* ((file (find-lisp-object-file-name sym 'defun))
(source (when file
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(re-search-forward (format "^(defun\\s +%s" (regexp-quote function-name)) nil t)
(when (re-search-backward "^(defun\\s +" nil t)
(cons file (line-number-at-pos)))))))
(if source
(format "Function '%s' is defined in:\n %s:%d"
function-name (car source) (cdr source))
(format "Function '%s' source location: %s"
function-name (or file "built-in/unknown"))))
(format "Error: Function '%s' not found" function-name))))
:args '((:name "function-name"
:type string
:description "The name of the function to locate"
:optional nil)))
(gptel-make-tool
:name "find_variable_source"
:category "emacs"
:description "Find the file where an Emacs Lisp variable is defined. Returns the file path if available."
:function (lambda (variable-name)
(let ((sym (intern-soft variable-name)))
(if (and sym (boundp sym))
(let ((file (or (find-lisp-object-file-name sym 'defvar)
(find-lisp-object-file-name sym 'defcustom)
(find-lisp-object-file-name sym 'defconst))))
(format "Variable '%s' is defined in: %s"
variable-name (or file "built-in/unknown")))
(format "Error: Variable '%s' not found" variable-name))))
:args '((:name "variable-name"
:type string
:description "The name of the variable to locate"
:optional nil)))
;; ============================================================================
;; Current Context Tools
;; ============================================================================
(gptel-make-tool
:name "get_buffer_context"
:category "emacs"
:description "Get information about the current buffer including its name, major mode, minor modes, and file path if applicable."
:function (lambda ()
(format "Buffer Context:\n Name: %s\n File: %s\n Major Mode: %s\n Minor Modes: %s\n Read-only: %s\n Modified: %s"
(buffer-name (current-buffer))
(or (buffer-file-name (current-buffer)) "N/A")
major-mode
(mapconcat (lambda (m) (format "%s" m))
(seq-filter (lambda (m) (and (boundp m) (symbol-value m)))
minor-mode-list)
", ")
(if buffer-read-only "yes" "no")
(if (buffer-modified-p) "yes" "no")))
:args nil)
(gptel-make-tool
:name "get_active_features"
:category "emacs"
:description "Get information about active Emacs features and packages."
:function (lambda ()
(format "Active Features (loaded packages/modules):\n%s"
(mapconcat (lambda (f) (format " - %s" f))
features
"\n")))
:args nil)
;; ============================================================================
;; Tool Registry for Easy Enable/Disable
;; ============================================================================
(defconst llm-tools-tool-list
'("get_function_doc"
"get_variable_doc"
"get_face_doc"
"describe_key"
"apropos_lookup"
"list_modes"
"find_function_source"
"find_variable_source"
"get_buffer_context"
"get_active_features")
"List of all LLM tool symbols defined in this file.")
;;;###autoload
(defun llm-tools-enable-all ()
"Enable all LLM Emacs tools for GPTel."
(interactive)
(dolist (tool llm-tools-tool-list)
(when (and tool (gptel-get-tool tool))
(add-to-list 'gptel-tools (gptel-get-tool tool))))
(message "LLM Emacs tools enabled for GPTel (%d tools)"
(length gptel-tools)))
;;;###autoload
(defun llm-tools-disable-all ()
"Disable all LLM Emacs tools from GPTel."
(interactive)
(dolist (tool llm-tools-tool-list)
(let ((tool-obj (gptel-get-tool tool)))
(when tool-obj
(setq gptel-tools (delete tool-obj gptel-tools)))))
(message "LLM Emacs tools disabled from GPTel"))
(provide 'llm-tools)
;;; llm-tools.el ends here