Skip to content

Instantly share code, notes, and snippets.

@tjohnman
Created December 24, 2024 00:07
Show Gist options
  • Save tjohnman/7fcb798584e7b20cc4f9b957c6676752 to your computer and use it in GitHub Desktop.
Save tjohnman/7fcb798584e7b20cc4f9b957c6676752 to your computer and use it in GitHub Desktop.
;;; 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