branch: elpa/aidermacs
commit c3ec966f0a7c6046ba62b8c1bdb2e5c26d71e310
Author: Mingde (Matthew) Zeng <[email protected]>
Commit: Mingde (Matthew) Zeng <[email protected]>
Refactor to use aidermacs--send-command callback effectively
---
README.org | 64 ++++++++++++++-----
aidermacs-models.el | 178 +++++++++++++++++++++++++---------------------------
aidermacs.el | 13 ++--
3 files changed, 141 insertions(+), 114 deletions(-)
diff --git a/README.org b/README.org
index c33d8aa1bb..9bb1ac49c6 100644
--- a/README.org
+++ b/README.org
@@ -31,35 +31,46 @@ While =aider.el= strictly mirrors Aider's CLI behavior,
=Aidermacs= is built aro
With =Aidermacs=, you get:
-1. Better Support for Multiline Input
- - =aider= is primarily designed as a command-line program, where multiline
input is restricted by terminal limitations.
- - Terminal-based tools require special syntax or manual formatting to
handle multiline input, which can be cumbersome and unintuitive.
- - =Aidermacs= eliminates these restrictions by handling multiline prompts
natively within Emacs, allowing you to compose complex AI requests just like
any other text input.
- - Whether you're pasting blocks of code or refining AI-generated responses,
multiline interactions in =Aidermacs= feel natural and seamless.
+1. Intelligent Model Selection
+ - Automatic discovery of available models from multiple providers
+ - Real-time model compatibility checking
+ - Seamless integration with your configured API keys
+ - Caching for quick access to frequently used models
+ - Support for both popular pre-configured models and dynamically discovered
ones
+
+2. Flexible Terminal Backend Support
+ - =Aidermacs= supports multiple terminal backends (comint and vterm) for
better compatibility and performance
+ - Easy configuration to choose your preferred terminal emulation
+ - Extensible architecture for adding new backends
-2. Smarter Syntax Highlighting
+3. Smarter Syntax Highlighting
- AI-generated code appears with proper syntax highlighting in major
languages.
- Ensures clarity and readability without additional configuration.
-3. Flexible Terminal Backend Support
- - =Aidermacs= supports multiple terminal backends (comint and vterm) for
better compatibility and performance
- - Easy configuration to choose your preferred terminal emulation
- - Extensible architecture for adding new backends
+4. Greater Configurability
+ - =Aidermacs= offers more customization options to tailor the experience to
your preferences.
+ - You can easily disable automatic commits, choose to operate only within a
specific directory, and customize keybindings.
+ - The model selection system provides fine-grained control over which AI
models are available and how they're used.
+ - Terminal backend selection allows choosing between comint and vterm for
different performance characteristics.
+ - Multiline input configuration adapts to your preferred editing style.
+
+5. Better Support for Multiline Input
+ - =aider= is primarily designed as a command-line program, where multiline
input is restricted by terminal limitations.
+ - Terminal-based tools require special syntax or manual formatting to
handle multiline input, which can be cumbersome and unintuitive.
+ - =Aidermacs= eliminates these restrictions by handling multiline prompts
natively within Emacs, allowing you to compose complex AI requests just like
any other text input.
+ - Whether you're pasting blocks of code or refining AI-generated responses,
multiline interactions in =Aidermacs= feel natural and seamless.
-4. Enhanced File Management from Emacs
+6. Enhanced File Management from Emacs
- List files currently in chat with =M-x aidermacs-list-added-files=
- Drop specific files from chat with =M-x aidermacs-drop-file=
- View output history with =M-x aidermacs-show-output-history=
- and more
-6. Greater Configurability
- - =Aidermacs= offers more customization options to tailor the experience
to your preferences.
- - You can easily disable automatic commits, choose to operate only within
a specific directory, and customize keybindings.
-
-5. Community-Driven Development
+7. Community-Driven Development
- =Aidermacs= is actively developed and maintained by the community,
incorporating user feedback and contributions.
- We prioritize features and improvements that directly benefit Emacs
users, ensuring a tool that evolves with your needs.
+
... and more to come 🚀
* Installation
@@ -205,6 +216,27 @@ The =.aider.prompt.org= file (created with =M-x
aidermacs-open-prompt-file=) is
The file is automatically recognized and enables Aidermacs minor mode with the
above keybindings.
+** Dynamic Model Selection
+
+Aidermacs provides intelligent model selection that automatically detects and
integrates with multiple AI providers:
+
+- Automatically fetches available models from supported providers (OpenAI,
Anthropic, DeepSeek, Google Gemini, OpenRouter)
+- Caches model lists for quick access
+- Supports both popular pre-configured models and dynamically discovered ones
+- Handles API keys and authentication automatically
+- Provides model compatibility checking
+
+To change models:
+1. Use =M-x aidermacs-change-model= or press =o= in the transient menu
+2. Select from either:
+ - Popular pre-configured models (fast)
+ - Dynamically fetched models from all supported providers (comprehensive)
+
+The system will automatically filter models to only show ones that are:
+- Supported by your current Aider version
+- Available through your configured API keys
+- Compatible with your current workflow
+
** Tips
1. Start with Core Actions to begin a session
diff --git a/aidermacs-models.el b/aidermacs-models.el
index 7cacd19606..28a712137f 100644
--- a/aidermacs-models.el
+++ b/aidermacs-models.el
@@ -22,104 +22,99 @@ Also based on aidermacs LLM benchmark:
https://aidermacs.chat/docs/leaderboards/
:type '(repeat string)
:group 'aidermacs-models)
-(defvar aidermacs--cached-models nil
+(defvar aidermacs--cached-models aidermacs-popular-models
"Cache of available AI models.")
(require 'json)
(require 'url)
(defun fetch-openai-compatible-models (url)
- "Fetch available models from an OpenAI compatible API endpoint at URL."
+ "Fetch available models from an OpenAI compatible API endpoint at URL.
+URL should be the base API endpoint, e.g. https://api.openai.com/v1.
+Returns a list of model names with appropriate prefixes based on the API
provider."
(let* ((url-parsed (url-generic-parse-url url))
(hostname (url-host url-parsed))
(prefix (cond ((string= hostname "api.openai.com") "openai")
- ((string= hostname "openrouter.ai") "openrouter")
- ((string= hostname "api.deepseek.com") "deepseek")
- ((string= hostname "api.anthropic.com") "anthropic")
- ((string= hostname "generativelanguage.googleapis.com")
"gemini")
- (t (error "Unknown API host: %s" hostname))))
+ ((string= hostname "openrouter.ai") "openrouter")
+ ((string= hostname "api.deepseek.com") "deepseek")
+ ((string= hostname "api.anthropic.com") "anthropic")
+ ((string= hostname "generativelanguage.googleapis.com")
"gemini")
+ (t (error "Unknown API host: %s" hostname))))
(token (cond ((string= hostname "api.openai.com") (getenv
"OPENAI_API_KEY"))
- ((string= hostname "openrouter.ai") (getenv
"OPENROUTER_API_KEY"))
- ((string= hostname "api.deepseek.com") (getenv
"DEEPSEEK_API_KEY"))
- ((string= hostname "api.anthropic.com") (getenv
"ANTHROPIC_API_KEY"))
- ((string= hostname "generativelanguage.googleapis.com")
(getenv "GEMINI_API_KEY"))
- (t (error "Unknown API host: %s" hostname)))))
- (with-current-buffer
- (let ((url-request-extra-headers
- (cond ((string= hostname "api.anthropic.com")
- `(("x-api-key" . ,token)
- ("anthropic-version" . "2023-06-01")))
- ((string= hostname "generativelanguage.googleapis.com")
- nil) ; No auth headers for Gemini, key is in URL
- (t
- `(("Authorization" . ,(concat "Bearer " token)))))))
- (url-retrieve-synchronously
- (if (string= hostname "generativelanguage.googleapis.com")
- (concat url "/models?key=" token)
- (concat url "/models"))))
- (goto-char url-http-end-of-headers)
- (let* ((json-object-type 'alist)
- (json-data (json-read))
- (models (if (string= hostname "generativelanguage.googleapis.com")
- (alist-get 'models json-data)
- (alist-get 'data json-data))))
- (mapcar (lambda (model)
- (concat prefix "/"
- (cond
- ((string= hostname
"generativelanguage.googleapis.com")
- (replace-regexp-in-string "^models/" "" (alist-get
'name model)))
- ((stringp model) model) ; Handle case where model
is just a string
- (t (or (alist-get 'id model)
- (alist-get 'name model))))))
- models)))))
+ ((string= hostname "openrouter.ai") (getenv
"OPENROUTER_API_KEY"))
+ ((string= hostname "api.deepseek.com") (getenv
"DEEPSEEK_API_KEY"))
+ ((string= hostname "api.anthropic.com") (getenv
"ANTHROPIC_API_KEY"))
+ ((string= hostname "generativelanguage.googleapis.com")
(getenv "GEMINI_API_KEY"))
+ (t (error "Unknown API host: %s" hostname)))))
+ (with-local-quit
+ (with-current-buffer
+ (let ((url-request-extra-headers
+ (cond ((string= hostname "api.anthropic.com")
+ `(("x-api-key" . ,token)
+ ("anthropic-version" . "2023-06-01")))
+ ((string= hostname "generativelanguage.googleapis.com")
+ nil) ; No auth headers for Gemini, key is in URL
+ (t
+ `(("Authorization" . ,(concat "Bearer " token)))))))
+ (url-retrieve-synchronously
+ (if (string= hostname "generativelanguage.googleapis.com")
+ (concat url "/models?key=" token)
+ (concat url "/models"))))
+ (goto-char url-http-end-of-headers)
+ (let* ((json-object-type 'alist)
+ (json-data (json-read))
+ (models (if (string= hostname
"generativelanguage.googleapis.com")
+ (alist-get 'models json-data)
+ (alist-get 'data json-data))))
+ (mapcar (lambda (model)
+ (concat prefix "/"
+ (cond
+ ((string= hostname
"generativelanguage.googleapis.com")
+ (replace-regexp-in-string "^models/" ""
(alist-get 'name model)))
+ ((stringp model) model) ; Handle case where
model is just a string
+ (t (or (alist-get 'id model)
+ (alist-get 'name model))))))
+ models))))))
-(defun aidermacs--get-supported-models ()
- "Get list of models supported by aider using the /models command."
- (let ((current-output aidermacs--current-output))
- (aidermacs--send-command-backend (get-buffer (aidermacs-buffer-name))
"/models /")
- ;; Wait briefly for output
- (sleep-for 0.5)
- (let* ((output aidermacs--current-output)
- (models (seq-filter
- (lambda (line)
- (string-prefix-p "- " line))
- (split-string output "\n" t))))
- (setq models (mapcar (lambda (line)
- (substring line 2)) ; Remove "- " prefix
- models))
- (message "Supported models: %S" models)
- models)))
-(defun aidermacs--get-available-models ()
- "Get list of available models from multiple providers, using cache if
available."
- (when (and aidermacs--cached-models
- (equal aidermacs--cached-models aidermacs-popular-models)
- (fboundp 'aidermacs-buffer-name)
- (get-buffer (aidermacs-buffer-name)))
- (setq aidermacs--cached-models nil))
+(defun aidermacs--select-model ()
+ "Private function for model selection with completion."
+ (let ((model (with-local-quit
+ (completing-read "Select AI model: " aidermacs--cached-models
nil t))))
+ (when model
+ (aidermacs--send-command (format "/model %s" model) t))))
- (unless aidermacs--cached-models
- (if (and (fboundp 'aidermacs-buffer-name)
- (get-buffer (aidermacs-buffer-name)))
- (let ((models nil)
- (supported-models (aidermacs--get-supported-models)))
- (dolist (url '("https://api.openai.com/v1"
- "https://openrouter.ai/api/v1"
- "https://api.deepseek.com"
- "https://api.anthropic.com/v1"
- "https://generativelanguage.googleapis.com/v1beta"))
- (condition-case err
- (let* ((fetched-models (fetch-openai-compatible-models url))
- (filtered-models (seq-filter (lambda (model)
+(defun aidermacs--get-available-models ()
+ "Get list of models supported by aider using the /models command."
+ (aidermacs--send-command
+ "/models /" t
+ (lambda (output)
+ (let* ((supported-models
+ (seq-filter
+ (lambda (line)
+ (string-prefix-p "- " line))
+ (split-string output "\n" t)))
+ (models nil))
+ (setq supported-models
+ (mapcar (lambda (line)
+ (substring line 2)) ; Remove "- " prefix
+ supported-models))
+ (dolist (url '("https://api.openai.com/v1"
+ "https://openrouter.ai/api/v1"
+ "https://api.deepseek.com"
+ "https://api.anthropic.com/v1"
+ "https://generativelanguage.googleapis.com/v1beta"))
+ (condition-case err
+ (let* ((fetched-models (fetch-openai-compatible-models url))
+ (filtered-models (seq-filter (lambda (model)
(member model
supported-models))
fetched-models)))
- (message "Fetched models from %s: %S" url fetched-models)
- (message "Filtered models from %s: %S" url filtered-models)
- (setq models (append models filtered-models)))
- (error (message "Failed to fetch models from %s: %s" url err))))
- (setq aidermacs--cached-models models))
- (setq aidermacs--cached-models aidermacs-popular-models)))
- aidermacs--cached-models)
+ ;; (message "Fetched models from %s: %S" url fetched-models)
+ ;; (message "Filtered models from %s: %S" url filtered-models)
+ (setq models (append models filtered-models)))
+ (error (message "Failed to fetch models from %s: %s" url err))))
+ (setq aidermacs--cached-models models)
+ (aidermacs--select-model)))))
(defun aidermacs-clear-model-cache ()
"Clear the cached models, forcing a fresh fetch on next use."
@@ -127,18 +122,19 @@ Also based on aidermacs LLM benchmark:
https://aidermacs.chat/docs/leaderboards/
(setq aidermacs--cached-models nil)
(message "Model cache cleared"))
-(defun aidermacs--select-model ()
- "Private function for model selection with completion."
- (let ((models (aidermacs--get-available-models)))
- (completing-read "Select AI model: " models nil t)))
-
;;;###autoload
(defun aidermacs-change-model ()
"Interactively select and change AI model in current aidermacs session."
(interactive)
- (let ((model (aidermacs--select-model)))
- (when model
- (aidermacs--send-command (format "/model %s" model) t))))
+ (when (and aidermacs--cached-models
+ (equal aidermacs--cached-models aidermacs-popular-models)
+ (fboundp 'aidermacs-buffer-name)
+ (get-buffer (aidermacs-buffer-name)))
+ (setq aidermacs--cached-models nil))
+
+ (if aidermacs--cached-models
+ (aidermacs--select-model)
+ (aidermacs--get-available-models)))
(provide 'aidermacs-models)
diff --git a/aidermacs.el b/aidermacs.el
index 21dd53e326..c7ee861204 100644
--- a/aidermacs.el
+++ b/aidermacs.el
@@ -185,6 +185,7 @@ This function can be customized or redefined by the user."
["Others"
("H" "Session History" aidermacs-show-output-history)
("C" "Copy Last Aidermacs Output" aidermacs-get-last-output)
+ ("O" "Clear Model Selection Cache" aidermacs-clear-model-cache)
("l" "Clear Buffer" aidermacs-clear)
("h" "Aider Help" aidermacs-help)]])
@@ -448,13 +449,11 @@ Sends the \"/ls\" command and returns the list of files
via callback."
(aidermacs--send-command
"/ls" t
(lambda (output)
- (condition-case nil
- (if-let* ((files (aidermacs--parse-ls-output output))
- (file (completing-read "Select file to drop: " files nil
t)))
- (progn
- (aidermacs--send-command (format "/drop %s" file)))
- (message "No files available to drop"))
- (quit (message "Drop file cancelled"))))))
+ (with-local-quit
+ (if-let* ((files (aidermacs--parse-ls-output output))
+ (file (completing-read "Select file to drop: " files nil t)))
+ (aidermacs--send-command (format "/drop %s" file))
+ (message "No files available to drop"))))))
;;;###autoload