Created
December 24, 2024 00:07
-
-
Save tjohnman/7fcb798584e7b20cc4f9b957c6676752 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; openrouter.el --- Use OpenRouter within Emacs -*- lexical-binding: t; -*- | |
;; This file is not part of GNU Emacs. | |
;; This program is free software: you can redistribute it and/or modify | |
;; it under the terms of the GNU Affero 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 Affero General Public License for more details. | |
;; You should have received a copy of the GNU Affero General Public License | |
;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
;;; Commentary: | |
;; How to use | |
;; Put this file in your load path and (require 'openrouter). Customize the openrouter group to set your API key and preferred model ID. | |
;; You can call openrouter-chat-interact to chat with the model. | |
;; Use openrouter-clear-chat-history to clear your chat history (it clears itself when you close Emacs, too). | |
;; You can change the model used temporarily by calling openrouter-select-model. | |
;; Assistant responses appear as messages at the moment, and no modes are defined by this package yet. This might change if I continue developing it, but there are much better alternatives out there, so it's possible this will remain a proof of concept. | |
;;; Code: | |
(defcustom openrouter--api-key nil "OpenRouter API key" :type 'string :group 'openrouter) | |
(defcustom openrouter--model-id "openrouter/auto" "The OpenRouter model ID used for chat." :type 'string :group 'openrouter) | |
(defvar openrouter--message-history [] | |
"The current chat message history with the OpenRouter model.") | |
(defun openrouter--get-models () | |
"Fetch and parse JSON from OpenRouter API, returning the list of models." | |
(let* ((url "https://openrouter.ai/api/v1/models") | |
(json-object-type 'hash-table) | |
(json-array-type 'vector) | |
(json-key-type 'string) | |
(json-data (with-current-buffer (url-retrieve-synchronously url) | |
(goto-char url-http-end-of-headers) | |
(json-read)))) | |
(gethash "data" json-data))) | |
(defun openrouter-select-model () | |
"Change the model used for chatting through OpenRouter." | |
(interactive) | |
(let* ((models (openrouter--get-models)) | |
(model-names (mapcar (lambda (model) | |
(gethash "name" model)) | |
models)) | |
(selected-name (completing-read "Select a model: " model-names nil t)) | |
(selected-model (seq-find (lambda (model) | |
(string= (gethash "name" model) selected-name)) | |
models))) | |
(when selected-model | |
(setq openrouter--model-id (gethash "id" selected-model)) | |
(message (concat "Model changed to " (gethash "name" selected-model)))))) | |
(defun openrouter--get-chat-completion-request (model messages) | |
"Call OpenRouter API for chat completion with MODEL and MESSAGES." | |
(let* ((url-request-method "POST") | |
(url-request-extra-headers | |
`(("Content-Type" . "application/json") | |
("Authorization" . ,(concat "Bearer " openrouter--api-key)))) | |
(url-request-data | |
(json-encode `(("model" . ,model) | |
("messages" . ,messages)))) | |
(buffer (url-retrieve-synchronously | |
"https://openrouter.ai/api/v1/chat/completions"))) | |
(with-current-buffer buffer | |
(goto-char url-http-end-of-headers) | |
(let ((json-object-type 'hash-table) | |
(json-array-type 'vector) | |
(json-key-type 'string)) | |
(json-read))))) | |
(defun openrouter--get-response-content (response) | |
"Extract the content from the OpenRouter API response." | |
(let* ((choices (gethash "choices" response)) | |
(first-choice (aref choices 0))) | |
(gethash "message" first-choice))) | |
(defun openrouter--get-chat-completion (model messages) | |
"Get a chat completion from the OpenRouter id MODEL on a chat history MESSAGES." | |
(openrouter--get-response-content (openrouter--get-chat-completion-request model messages))) | |
(defun openrouter-chat-interact () | |
"Interact with OpenRouter chat model." | |
(interactive) | |
(let* ((user-message (read-string "Your message: ")) | |
(user-turn (vector `((role . "user") (content . ,user-message)))) | |
(updated-history (vconcat openrouter--message-history user-turn)) | |
(response (openrouter--get-chat-completion openrouter--model-id updated-history)) | |
(assistant-turn (vector `((role . ,(gethash "role" response)) | |
(content . ,(gethash "content" response)))))) | |
;; Update the message history | |
(setq openrouter--message-history (vconcat updated-history assistant-turn)) | |
;; Display the assistant's response | |
(message "Assistant: %s" (gethash "content" response)))) | |
(defun openrouter-clear-chat-history () | |
"Clears the chat history with the OpenRouter model." | |
(interactive) | |
(setq openrouter--message-history [])) | |
(provide 'openrouter) | |
;;; openrouter.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment