;;; gptel-kagi.el --- Kagi support for gptel -*- lexical-binding: t; -*- ;; Copyright (C) 2023 Karthik Chikmagalur ;; Author: Karthik Chikmagalur ;; Keywords: hypermedia ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This file adds support for the Kagi FastGPT LLM API to gptel ;;; Code: (require 'gptel) (require 'cl-generic) (eval-when-compile (require 'cl-lib)) ;;; Kagi (cl-defstruct (gptel-kagi (:constructor gptel--make-kagi) (:copier nil) (:include gptel-backend))) (cl-defmethod gptel--parse-response ((_backend gptel-kagi) response info) (let* ((data (plist-get response :data)) (output (plist-get data :output)) (references (plist-get data :references))) (when references (setq references (cl-loop with linker = (pcase (buffer-local-value 'major-mode (plist-get info :buffer)) ('org-mode (lambda (text url) (format "[[%s][%s]]" url text))) ('markdown-mode (lambda (text url) (format "[%s](%s)" text url))) (_ (lambda (text url) (buttonize text (lambda (data) (browse-url data)) url)))) for ref across references for title = (plist-get ref :title) for snippet = (plist-get ref :snippet) for url = (plist-get ref :url) for n upfrom 1 collect (concat (format "[%d] " n) (funcall linker title url) ": " (replace-regexp-in-string "" "*" snippet)) into ref-strings finally return (concat "\n\n" (mapconcat #'identity ref-strings "\n"))))) (concat output references))) (cl-defmethod gptel--request-data ((_backend gptel-kagi) prompts) "JSON encode PROMPTS for Kagi." (pcase-exhaustive gptel-model ("fastgpt" `(,@prompts :web_search t :cache t)) ((and model (guard (string-prefix-p "summarize" model))) `(,@prompts :engine ,(substring model 10))))) (cl-defmethod gptel--parse-buffer ((_backend gptel-kagi) &optional _max-entries) (let ((url (or (thing-at-point 'url) (get-text-property (point) 'shr-url) (get-text-property (point) 'image-url))) ;; (filename (thing-at-point 'existing-filename)) ;no file upload support yet (prop (text-property-search-backward 'gptel 'response (when (get-char-property (max (point-min) (1- (point))) 'gptel) t)))) (if (and url (string-prefix-p "summarize" gptel-model)) (list :url url) (if (and (prop-match-p prop) (prop-match-value prop)) (user-error "No user prompt found!") (let ((prompts (string-trim (buffer-substring-no-properties (prop-match-beginning prop) (prop-match-end prop)) (format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*" (regexp-quote (gptel-prompt-prefix-string))) (format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*" (regexp-quote (gptel-response-prefix-string)))))) (pcase-exhaustive gptel-model ("fastgpt" (setq prompts (list :query (if (prop-match-p prop) (concat ;; Fake a system message by including it in the prompt gptel--system-message "\n\n" prompts) "")))) ((and model (guard (string-prefix-p "summarize" model))) ;; If the entire contents of the prompt looks like a url, send the url ;; Else send the text of the region (setq prompts (if-let (((prop-match-p prop)) (engine (substring model 10))) ;; It's a region of text (list :text prompts) "")))) prompts))))) ;;;###autoload (cl-defun gptel-make-kagi (name &key curl-args stream key (host "kagi.com") (header (lambda () `(("Authorization" . ,(concat "Bot " (gptel--get-api-key)))))) (models '("fastgpt" "summarize:cecil" "summarize:agnes" "summarize:daphne" "summarize:muriel")) (protocol "https") (endpoint "/api/v0/")) "Register a Kagi FastGPT backend for gptel with NAME. Keyword arguments: CURL-ARGS (optional) is a list of additional Curl arguments. HOST is the Kagi host (with port), defaults to \"kagi.com\". MODELS is a list of available Kagi models: only fastgpt is supported. STREAM is a boolean to toggle streaming responses, defaults to false. Kagi does not support a streaming API yet. PROTOCOL (optional) specifies the protocol, https by default. ENDPOINT (optional) is the API endpoint for completions, defaults to \"/api/v0/fastgpt\". HEADER (optional) is for additional headers to send with each request. It should be an alist or a function that retuns an alist, like: ((\"Content-Type\" . \"application/json\")) KEY (optional) is a variable whose value is the API key, or function that returns the key. Example: ------- (gptel-make-kagi \"Kagi\" :key my-kagi-key)" (declare (indent 1)) stream ;Silence byte-compiler (let ((backend (gptel--make-kagi :curl-args curl-args :name name :host host :header header :key key :models models :protocol protocol :endpoint endpoint :url (lambda () (concat protocol "://" host endpoint (if (equal gptel-model "fastgpt") "fastgpt" "summarize")))))) (prog1 backend (setf (alist-get name gptel--known-backends nil nil #'equal) backend)))) (provide 'gptel-kagi) ;;; gptel-kagi.el ends here