From c835491b716e2c1243c124a65e01b22f641657c4 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 22 May 2026 15:34:07 -0500 Subject: fix(ai-config): require gptel backend libs so the fork's constructors load cj/toggle-gptel and gptel chat errored with "Symbol's function definition is void: gptel-make-anthropic". The local gptel fork on :load-path with :ensure nil ships no generated autoloads, so (require 'gptel) loads gptel.el but never gptel-anthropic.el or gptel-openai.el, where the gptel-make-* constructors live. cj/ensure-gptel-backends then reached gptel-make-anthropic before it was defined. cj/ensure-gptel-backends now requires gptel-anthropic and gptel-openai first, through a small cj/--gptel-load-backend-libs helper. Verified end-to-end: with the fork on load-path, the constructors are fbound and both backends build. --- modules/ai-config.el | 12 ++++++- tests/test-ai-config-gptel-backend-libs.el | 58 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/test-ai-config-gptel-backend-libs.el diff --git a/modules/ai-config.el b/modules/ai-config.el index ddbe91b8..8cf70ee4 100644 --- a/modules/ai-config.el +++ b/modules/ai-config.el @@ -122,9 +122,19 @@ HOST and USER must be strings that identify the credential to return." (setq cj/openai-api-key-cached (cj/auth-source-secret "api.openai.com" "apikey")))) +(defun cj/--gptel-load-backend-libs () + "Require the gptel backend libraries so their `gptel-make-*' constructors exist. +The local fork (`:load-path \"~/code/gptel\"', `:ensure nil') ships no generated +autoloads, so requiring `gptel' alone never loads `gptel-anthropic' / +`gptel-openai', where the constructors are defined." + (require 'gptel-anthropic) + (require 'gptel-openai)) + (defun cj/ensure-gptel-backends () "Initialize GPTel backends if they are not already available. -Call this only after loading `gptel' so the backend constructors exist." +Loads the backend libraries first so the `gptel-make-*' constructors are +defined even when gptel is the local fork without generated autoloads." + (cj/--gptel-load-backend-libs) (unless gptel-claude-backend (setq gptel-claude-backend (gptel-make-anthropic diff --git a/tests/test-ai-config-gptel-backend-libs.el b/tests/test-ai-config-gptel-backend-libs.el new file mode 100644 index 00000000..cbf48f44 --- /dev/null +++ b/tests/test-ai-config-gptel-backend-libs.el @@ -0,0 +1,58 @@ +;;; test-ai-config-gptel-backend-libs.el --- Tests for gptel backend-lib loading -*- lexical-binding: t; -*- + +;;; Commentary: +;; Regression coverage for the "gptel-make-anthropic void" bug. The local +;; gptel fork (:load-path "~/code/gptel", :ensure nil) ships no generated +;; autoloads, so (require 'gptel) alone never loads gptel-anthropic / +;; gptel-openai where the gptel-make-* constructors live. The fix is to +;; require those backend libraries explicitly before constructing backends. +;; +;; These tests don't load gptel itself (it isn't reliably loadable in batch); +;; they stub `require' and the constructors to verify the loader requires both +;; libs and that `cj/ensure-gptel-backends' calls it before building backends. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'ai-config) + +;; gptel defvars these at runtime; declare them here so the wiring test can +;; let-bind them in a batch session where gptel itself is not loaded. +(defvar gptel-backend) +(defvar gptel-model) + +(ert-deftest test-ai-config-gptel-load-backend-libs-requires-both () + "Normal: the loader requires gptel-anthropic and gptel-openai so the fork's +make-* constructors exist despite the missing autoloads." + (let ((required '())) + (cl-letf (((symbol-function 'require) + (lambda (feature &rest _) (push feature required) feature))) + (cj/--gptel-load-backend-libs)) + (should (memq 'gptel-anthropic required)) + (should (memq 'gptel-openai required)))) + +(ert-deftest test-ai-config-ensure-gptel-backends-loads-libs-first () + "Regression: `cj/ensure-gptel-backends' loads the backend libs before it +calls the constructors, so a fork without autoloads no longer signals +`void-function gptel-make-anthropic'." + (let ((loaded nil) + (gptel-claude-backend nil) + (gptel-chatgpt-backend nil) + (gptel-backend nil) + (gptel-model nil)) + (cl-letf (((symbol-function 'cj/--gptel-load-backend-libs) + (lambda () (setq loaded t))) + ((symbol-function 'gptel-make-anthropic) (lambda (&rest _) 'claude)) + ((symbol-function 'gptel-make-openai) (lambda (&rest _) 'chatgpt)) + ((symbol-function 'cj/anthropic-api-key) (lambda () "k")) + ((symbol-function 'cj/openai-api-key) (lambda () "k"))) + (cj/ensure-gptel-backends)) + (should loaded) + (should (eq gptel-claude-backend 'claude)) + (should (eq gptel-chatgpt-backend 'chatgpt)))) + +(provide 'test-ai-config-gptel-backend-libs) +;;; test-ai-config-gptel-backend-libs.el ends here -- cgit v1.2.3