347 lines
16 KiB
EmacsLisp
347 lines
16 KiB
EmacsLisp
;;; 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
|