branch: elpa/aidermacs commit c3ec966f0a7c6046ba62b8c1bdb2e5c26d71e310 Author: Mingde (Matthew) Zeng <matthew...@posteo.net> Commit: Mingde (Matthew) Zeng <matthew...@posteo.net>
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