;;; init-ai.el --- Configuración de inteligencias artificales -*- lexical-binding: t -*- ;; Author: kj ;; URL: https://git.kj2.me/kj/confi-emacs-actual ;;; Commentary: ;; Configuración para Inteligencia artifical en Emacs. ;;; Code: ;; Cliente LLM (ollama, chatgpt, gemini, etc.) (use-package gptel :defer nil :config (when (getenv "GEMINI_KEY") (gptel-make-gemini "Gemini" :key (getenv "GEMINI_KEY") :stream t)) (gptel-make-openai "llama.cpp" :stream t :protocol "http" :host "127.0.0.1:1945" :models '("Qwen3.8-27B" "gemma-4-12B" "gemma-4-E2B" "gemma-4-E4B" "gptoss-20b")) (gptel-make-ollama "Ollama" :host "localhost:11434" :stream t :request-params '(:think :json-false) :models '("gemma4:31b-cloud" "glm-5.1:cloud" "kimi-k2.6:cloud" "gpt-oss:20b-cloud" "minimax-m2.5:cloud" "qwen3-coder-next:cloud")) ;; Backend opencode-go: suscripción OpenCode Go vía endpoint OpenAI-compatible (defun kj-opencode-go-key () "Return the OpenCode Go API key from opencode's auth file." (let ((auth (with-temp-buffer (insert-file-contents "~/.local/share/opencode/auth.json") (json-parse-buffer :object-type 'alist)))) (alist-get 'key (alist-get 'opencode-go auth)))) (gptel-make-openai "OpenCode Go" :host "opencode.ai" :endpoint "/zen/go/v1/chat/completions" :key #'kj-opencode-go-key :stream t :models '(minimax-m3 minimax-m2.7 minimax-m2.5 kimi-k3 kimi-k2.7-code kimi-k2.6 longcat-2.0 kimi-k2.5 glm-5.3-flash glm-5.3 glm-5.2 glm-5.1 glm-5 deepseek-v4-pro deepseek-v4-flash deepseek-v4-flash-vision-exp qwen3.8-max qwen3.7-max qwen3.7-plus qwen3.6-plus qwen3.5-plus mimo-v2-pro mimo-v2-omni mimo-v2.5-pro mimo-v2.5 hy3 hy3-preview gpt-5.6-luna grok-4.5 grok-4.6 muse-spark-1.2-contributor)) ;; Modelos gratuitos de OpenCode Go, servidos vía el endpoint /zen/v1 (gptel-make-openai "OpenCode Free" :host "opencode.ai" :endpoint "/zen/v1/chat/completions" :key #'kj-opencode-go-key :stream t :models '(big-pickle deepseek-v4-flash-free muse-spark-1.2-contributor-free mimo-v2.5-free hy3-free nemotron-3-ultra-free nemotron-3.5-lightning-free laguna-s-2.1-free)) ;; Backend y modelo por defecto (setq gptel-backend (gptel-get-backend "OpenCode Go") gptel-model 'mimo-v2.5) (setq gptel-default-mode 'markdown-mode gptel-prompt-prefix-alist '((markdown-mode . "# User\n\n") (org-mode . "* User\n\n") (text-mode . "# User\n\n")) gptel-response-prefix-alist '((markdown-mode . "# AI\n\n") (org-mode . "* AI\n\n") (text-mode . "# AI\n\n")) gptel-directives '((default . "You are a large language model living in Emacs and a helpful assistant. Respond concisely in Spanish.")) ) (add-hook 'gptel-post-response-functions 'gptel-end-of-response) ;; El "thinking" de los modelos se muestra en los buffers gptel. ;; `ignore' (valor por defecto de gptel) lo muestra pero NO lo reenvía ;; al modelo en turnos posteriores (recomendado: evita llenar el ;; contexto, ver NEWS de gptel). Usar `t' si además se quiere ;; reenviarlo y `nil' para deshabilitarlo (útil para respuestas mas rápidas). (setopt gptel-include-reasoning 'ignore) (defun gptel-switch+model () "Switch to gptel backend and model in a single completion prompt." (interactive) (let (choices) (dolist (pair gptel--known-backends) (let* ((backend-name (car pair)) (backend (cdr pair)) (models (and (fboundp 'gptel-backend-models) (gptel-backend-models backend)))) (when models (dolist (model models) (push (cons ; (format "%s:%s" backend-name model) (format "%s → %s" (propertize backend-name 'face 'font-lock-keyword-face) (propertize (symbol-name model) 'face 'font-lock-function-name-face)) (cons backend-name model)) choices))))) (let* ((choice (completing-read "Model: " (mapcar #'car choices) nil t)) (sel (cdr (assoc choice choices)))) (setq gptel-backend (cdr (assoc (car sel) gptel--known-backends)) gptel-model (cdr sel)) (message "gptel set to %s:%s" (car sel) (cdr sel))))) ;; Al cargar gptel, cargar gptel-agent para que sus presets estén ;; disponibles (p. ej. al reabrir una sesión guardada de agente). (require 'gptel-agent) ) ;; (use-package copilot ;; :hook (prog-mode . copilot-mode) ;; :bind (:map copilot-completion-map ;; ("C-g" . 'copilot-clear-overlay) ;; ("C-" . 'copilot-accept-completion) ;; ("S-" . 'copilot-accept-completion-by-word))) (use-package gptel-magit :ensure t :hook (magit-mode . gptel-magit-install) :config ;; gptel-magit no sabe manejar el contenido de "reasoning": gptel entrega ;; el thinking al callback como (reasoning . TEXTO) y gptel-magit lo ;; inserta como si fuera la respuesta final, fallando con ;; Wrong type argument: char-or-string-p, (reasoning . "...") ;; (https://github.com/ragnard/gptel-magit/issues/8). Esto ocurre ;; aunque `gptel-include-reasoning' sea nil, porque gptel entrega las ;; celdas (reasoning . ...) al callback de forma incondicional (solo ;; controla si se insertan en el buffer gptel y si se reenvían). ;; En vez de deshabilitar el thinking globalmente, sanitizamos la ;; respuesta en el callback: se descartan las celdas de reasoning y solo ;; se deja pasar la respuesta final (string). Así el thinking sigue ;; activo en las sesiones gptel normales. (defun kj-gptel-magit--string-response-callback (callback) "Return a callback that forwards only string responses to CALLBACK. Discards the (reasoning . TEXT) cells through which gptel delivers the thinking content to the callback (see `gptel-request')." (lambda (response &rest args) (when (stringp response) (apply callback response args)))) (defun kj-gptel-magit--sanitize-callback (orig-fn &rest args) "Around advice for `gptel-magit--request'. Wraps the :callback argument of ARGS so that responses with reasoning content do not break gptel-magit." (let ((pos (cl-position :callback args))) (when (and pos (functionp (nth (1+ pos) args))) (setf (nth (1+ pos) args) (kj-gptel-magit--string-response-callback (nth (1+ pos) args))))) (apply orig-fn args)) (advice-add 'gptel-magit--request :around #'kj-gptel-magit--sanitize-callback)) (use-package gptel-autocomplete :defer nil :ensure (:host github :repo "JDNdeveloper/gptel-autocomplete") :bind (("M-" . gptel-complete) :map gptel-autocomplete-completion-map ("C-" . gptel-accept-completion))) (use-package macher :ensure (:host github :repo "kmontag/macher") :custom (macher-action-buffer-ui 'org)) ;; agent-shell: interfaz nativa de Emacs para interactuar con agentes ACP (use-package shell-maker) (use-package acp) (use-package agent-shell :after (shell-maker acp) :hook (elpaca-after-init . (lambda () (defalias 'opencode #'agent-shell-opencode-start-agent))) :config (setq agent-shell-opencode-authentication (agent-shell-opencode-make-authentication :none t)) (setq agent-shell-opencode-default-model-id nil) ;; Desactivar guardado de transcripciones (setq agent-shell-transcript-file-path-function nil)) (provide 'init-ai) ;;; init-ai.el ends here