From 08bca36662adfb0a9dacaff09c0b2f9b6764deb7 Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Mon, 16 Sep 2024 16:57:24 +0200 Subject: [PATCH 01/10] gptel-backend This is all generated code that has been lightly tested. It likely has lots of issues and needs to be able to do more. But for now I will be using this while I develop and improve it as I find issues. Why this instead of emacs-cody? Personally I prefer chat over autocomplete. gptel provides fantastic hooks all over emacs for chatting. I also see this as a useful backend to include our smart context in the future. --- README.org | 14 +++++ gptel-cody.el | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 gptel-cody.el diff --git a/README.org b/README.org index 5ad336e..6f5fa22 100644 --- a/README.org +++ b/README.org @@ -68,3 +68,17 @@ Autocompletion is the main use case for now. Chat is just a stub. - `M-x cody-login` will start Cody up and apply it to applicable buffers. - `M-x cody-logout` shuts Cody down. - `M-x cody-mode` toggles Cody on and off in a given buffer. + +* PREVIEW gptel support + +We have an experimental backend for [[https://github.com/karthink/gptel][gptel]]. + +#+begin_src emacs-lisp + (require 'gptel-cody) + + ;; Set Cody as the default backend + (setq gptel-backend + (gptel-make-cody "Cody" + :key "sgp_ADD_DOTCOM_TOKEN_HERE" + :stream t)) +#+end_src diff --git a/gptel-cody.el b/gptel-cody.el new file mode 100644 index 0000000..b32c32e --- /dev/null +++ b/gptel-cody.el @@ -0,0 +1,152 @@ +;;; gptel-cody.el --- Cody support for gptel -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Sourcegraph + +;; Author: Keegan Carruthers-Smith + +;; 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 Cody API to gptel + +;;; Code: +(require 'gptel) + +(cl-defstruct (gptel-cody (:constructor gptel--make-cody) + (:copier nil) + (:include gptel-backend))) + +(cl-defmethod gptel-curl--parse-stream ((_backend gptel-cody) _info) + "Parse Cody's streaming response." + (let ((content-strs)) + (condition-case nil + (while (re-search-forward "^data:" nil t) + (when-let* ((response (gptel--json-read)) + (delta (plist-get response :deltaText))) + (push delta content-strs))) + (error + (goto-char (match-beginning 0)))) + (apply #'concat (nreverse content-strs)))) + +(cl-defmethod gptel--parse-response ((_backend gptel-cody) response _info) + "Parse Cody's RESPONSE." + (plist-get response :deltaText)) + +(cl-defmethod gptel--request-data ((_backend gptel-cody) prompts) + "Prepare REQUEST-DATA for Cody API." + `(:model "anthropic/claude-3-5-sonnet-20240620" + :messages ,(vconcat prompts) + :maxTokensToSample ,(or gptel-max-tokens 4000) + :temperature ,(or gptel-temperature 0) + :topK -1 + :topP -1 + :stopSequences [""] + :stream ,(or (and gptel-stream gptel-use-curl + (gptel-backend-stream gptel-backend)) + :json-false))) + +(cl-defmethod gptel--parse-buffer ((_backend gptel-cody) &optional max-entries) + "Parse current buffer backwards from point and return a list of prompts. + +MAX-ENTRIES is the number of queries/responses to include for context." + (let ((prompts) (prop)) + (while (and + (or (not max-entries) (>= max-entries 0)) + (setq prop (text-property-search-backward + 'gptel 'response + (when (get-char-property (max (point-min) (1- (point))) + 'gptel) + t)))) + (push (list :speaker (if (prop-match-value prop) "assistant" "human") + :text + (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))))) + prompts) + (and max-entries (cl-decf max-entries))) + (cons (list :speaker "system" + :text gptel--system-message) + prompts))) + +;;;###autoload +(cl-defun gptel-make-cody + (name &key + (header (lambda () + (when-let (key (gptel--get-api-key)) + `(("Authorization" . ,(concat "token " key)) + ("Content-Type" . "application/json") + ("Accept-Encoding" . "gzip;q=0") + ("User-Agent" . "edit / v1"))))) + (host "sourcegraph.com") + (protocol "https") + (endpoint "/.api/completions/stream") + (stream t) + (models '("anthropic/claude-3-5-sonnet-20240620" + "anthropic/claude-3-opus-20240229" + "openai/gpt-4o" + "google/gemini-1.5-pro" + "openai/cody-chat-preview-001" + "openai/cody-chat-preview-002" + "google/gemini-1.5-flash" + "anthropic/claude-3-haiku-20240307" + "fireworks/accounts/fireworks/models/mixtral-8x7b-instruct")) + (key 'gptel-api-key) + curl-args) + "Create a Cody API backend for gptel. + +NAME is a string to identify this backend. + +Keyword arguments: + +CURL-ARGS (optional) is a list of additional curl arguments. + +HOST (optional) is the API host, defaults to \"sourcegraph.com\". + +MODELS is a list of available model names. + +STREAM is a boolean to toggle streaming responses, defaults to t. + +PROTOCOL (optional) specifies the protocol, https by default. + +ENDPOINT (optional) is the API endpoint for completions. + +HEADER (optional) is for additional headers to send with each +request. It should be an alist or a function that returns an +alist. + +KEY (optional) is a variable whose value is the API key, or +function that returns the key." + (declare (indent 1)) + (let ((backend (gptel--make-cody + :curl-args curl-args + :name name + :host host + :header header + :key key + :models models + :protocol protocol + :endpoint endpoint + :stream stream + :url (concat protocol "://" host endpoint "?api-version=2&client-name=gptel&client-version=v1")))) + (setf (alist-get name gptel--known-backends + nil nil #'equal) + backend))) + +(provide 'gptel-cody) +;;; gptel-cody.el ends here From c717527d664d85bd4c59556a065cd3ca789e85ba Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Tue, 17 Sep 2024 09:04:11 +0200 Subject: [PATCH 02/10] TODOs for gptel --- README.org | 14 ++++ context-discussion.md | 155 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 context-discussion.md diff --git a/README.org b/README.org index 6f5fa22..f780416 100644 --- a/README.org +++ b/README.org @@ -82,3 +82,17 @@ We have an experimental backend for [[https://github.com/karthink/gptel][gptel]] :key "sgp_ADD_DOTCOM_TOKEN_HERE" :stream t)) #+end_src + +** TODO ensure model selection works +Right now I think I am just hardcoding it at request time. + +** TODO cody system prompts + +** TODO make it possible to change instance (test against s2) +** TODO see if we can plug into same auth source +Maybe also parse vscode state? Right now it is all in a sqlite file. +** TODO enhanced context for enterprise +[[file:context-discussion.md][context-discussions.md]] +** TODO model list from backend? +I think we have this sort of functionality, but I do see a lot of hardcoding +in the cody client. diff --git a/context-discussion.md b/context-discussion.md new file mode 100644 index 0000000..6ad44b7 --- /dev/null +++ b/context-discussion.md @@ -0,0 +1,155 @@ +### does gptel make it possible to extend its context sources. Right now in its transient menu it offers adding a file or a buffer. I want to do a remote API call to retrieve context. Is that possible to do? + +Yes, gptel does make it possible to extend its context sources, and you can add a remote API call to retrieve context. While this functionality isn't directly exposed in the transient menu, you can extend gptel's context capabilities by: + +1. Creating a new function to fetch context from your remote API. +2. Adding this context to gptel's context system. +3. Optionally, adding a new option to the transient menu. + +Here's how you could approach this: + +1. Create a function to fetch and add context from your API: + +```elisp +(defun my-gptel-add-api-context () + "Fetch context from a remote API and add it to gptel's context." + (interactive) + (let ((api-response (my-fetch-from-api))) ; Replace with your API call + (with-temp-buffer + (insert api-response) + (gptel-context-add)))) +``` + +2. The `gptel-context-add` function (aliased as `gptel-add`) is flexible and can work with different types of input. By calling it in a temporary buffer with the API response, you're effectively adding the API response as context. + +3. If you want to add this to the transient menu, you can advise the `gptel-menu` function to add your new option: + +```elisp +(defun my-gptel-menu-add-api-option (orig-fun &rest args) + "Advice to add API context option to gptel-menu." + (let ((transient-current-prefix 'gptel-menu)) + (transient-append-suffix 'gptel-menu "Context" + '("a" "Add API context" my-gptel-add-api-context)))) + +(advice-add 'gptel-menu :around #'my-gptel-menu-add-api-option) +``` + +This will add a new option "a" to add API context in the "Context" section of the gptel menu. + +4. If you want more control over how the API context is formatted or stored, you can modify the `gptel-context--alist` directly: + +```elisp +(defun my-gptel-add-api-context () + "Fetch context from a remote API and add it to gptel's context." + (interactive) + (let ((api-response (my-fetch-from-api))) + (push (cons "API Context" api-response) gptel-context--alist))) +``` + +5. You might also want to modify the `gptel-context--wrap-default` function or set a custom `gptel-context-wrap-function` to handle your new context type appropriately. + +By implementing these extensions, you can effectively add a remote API as a context source for gptel, making it available through both programmatic calls and the transient menu interface. + +### I want to process the prompt by the user and inspect things like the current repository the user is in to decide what context to include when "enhanced" context is on. Adjust your response to account for that. + +Certainly! Your requirement to process the user's prompt and inspect the current environment (like the Git repository) to decide on context is a great use case for enhancing gptel's functionality. Here's an adjusted approach that takes this into account: + +1. First, let's create a function to gather enhanced context: + +```elisp +(defun my-gptel-gather-enhanced-context (prompt) + "Gather enhanced context based on PROMPT and current environment." + (let ((context "")) + ;; Check if we're in a Git repository + (when (vc-git-root default-directory) + (setq context (concat context "Current Git repository: " + (vc-git-root default-directory) "\n"))) + + ;; Analyze the prompt for keywords + (cond + ((string-match-p "\\bfile\\b" prompt) + (setq context (concat context "Current file: " (buffer-file-name) "\n"))) + ((string-match-p "\\bproject\\b" prompt) + (setq context (concat context "Project root: " + (project-root (project-current)) "\n"))) + ((string-match-p "\\bfunction\\b" prompt) + (setq context (concat context "Current function: " + (which-function) "\n")))) + + ;; You could add more conditions here based on the prompt or environment + + context)) +``` + +2. Now, let's modify gptel's context wrapping function to include this enhanced context: + +```elisp +(defun my-gptel-context-wrap-function (message contexts) + "Wrap MESSAGE with CONTEXTS and enhanced context." + (let* ((enhanced-context (my-gptel-gather-enhanced-context message)) + (original-context (gptel-context--string contexts)) + (combined-context (concat enhanced-context original-context))) + (if (> (length combined-context) 0) + (pcase gptel-use-context + ('system (concat message "\n\n" combined-context)) + ('user (concat combined-context "\n\n" message)) + ('nil message)) + message))) + +(setq gptel-context-wrap-function #'my-gptel-context-wrap-function) +``` + +3. To make this "enhanced" context toggleable, we can add a new option to gptel's menu: + +```elisp +(defvar my-gptel-use-enhanced-context nil + "Whether to use enhanced context in gptel queries.") + +(defun my-gptel-toggle-enhanced-context () + "Toggle enhanced context for gptel." + (interactive) + (setq my-gptel-use-enhanced-context (not my-gptel-use-enhanced-context)) + (message "Enhanced context %s" + (if my-gptel-use-enhanced-context "enabled" "disabled"))) + +(defun my-gptel-menu-add-enhanced-context-option (orig-fun &rest args) + "Advice to add enhanced context toggle to gptel-menu." + (let ((transient-current-prefix 'gptel-menu)) + (transient-append-suffix 'gptel-menu "Context" + '("e" "Toggle enhanced context" my-gptel-toggle-enhanced-context)))) + +(advice-add 'gptel-menu :around #'my-gptel-menu-add-enhanced-context-option) +``` + +4. Finally, update the context wrap function to respect this toggle: + +```elisp +(defun my-gptel-context-wrap-function (message contexts) + "Wrap MESSAGE with CONTEXTS and enhanced context if enabled." + (let* ((enhanced-context (if my-gptel-use-enhanced-context + (my-gptel-gather-enhanced-context message) + "")) + (original-context (gptel-context--string contexts)) + (combined-context (concat enhanced-context original-context))) + (if (> (length combined-context) 0) + (pcase gptel-use-context + ('system (concat message "\n\n" combined-context)) + ('user (concat combined-context "\n\n" message)) + ('nil message)) + message))) +``` + +This approach allows you to: + +1. Gather enhanced context based on the user's prompt and current environment. +2. Integrate this enhanced context with gptel's existing context system. +3. Toggle the use of enhanced context via the gptel menu. +4. Process each prompt to decide what context to include when enhanced context is enabled. + +You can further expand the `my-gptel-gather-enhanced-context` function to include more sophisticated analysis of the prompt or to make API calls if necessary. This setup provides a flexible framework for adding intelligent, context-aware enhancements to gptel queries. + + + + + + From a7eb6b386cad9ed1d65bec6dcd99a9a713182089 Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Tue, 17 Sep 2024 09:51:28 +0200 Subject: [PATCH 03/10] use passed in model --- README.org | 10 +++++++++- gptel-cody.el | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.org b/README.org index f780416..6fda7f0 100644 --- a/README.org +++ b/README.org @@ -83,9 +83,12 @@ We have an experimental backend for [[https://github.com/karthink/gptel][gptel]] :stream t)) #+end_src -** TODO ensure model selection works +** DONE ensure model selection works Right now I think I am just hardcoding it at request time. +As follow-up need to handle errors querying the backend to find out which +models are allowed by the logged in user. + ** TODO cody system prompts ** TODO make it possible to change instance (test against s2) @@ -93,6 +96,11 @@ Right now I think I am just hardcoding it at request time. Maybe also parse vscode state? Right now it is all in a sqlite file. ** TODO enhanced context for enterprise [[file:context-discussion.md][context-discussions.md]] +** TODO handle errors from backend ** TODO model list from backend? I think we have this sort of functionality, but I do see a lot of hardcoding in the cody client. + +This should be possible, since I just tried with a random model on my free +account and got this response from the backend +: the requested chat model is not available ("openai/gpt-4o", onProTier=false, hasFeatureFlags=false) diff --git a/gptel-cody.el b/gptel-cody.el index b32c32e..52946a9 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -46,16 +46,16 @@ (cl-defmethod gptel--request-data ((_backend gptel-cody) prompts) "Prepare REQUEST-DATA for Cody API." - `(:model "anthropic/claude-3-5-sonnet-20240620" - :messages ,(vconcat prompts) - :maxTokensToSample ,(or gptel-max-tokens 4000) - :temperature ,(or gptel-temperature 0) - :topK -1 - :topP -1 - :stopSequences [""] - :stream ,(or (and gptel-stream gptel-use-curl - (gptel-backend-stream gptel-backend)) - :json-false))) + `(:model ,gptel-model + :messages ,(vconcat prompts) + :maxTokensToSample ,(or gptel-max-tokens 4000) + :temperature ,(or gptel-temperature 0) + :topK -1 + :topP -1 + :stopSequences [""] + :stream ,(or (and gptel-stream gptel-use-curl + (gptel-backend-stream gptel-backend)) + :json-false))) (cl-defmethod gptel--parse-buffer ((_backend gptel-cody) &optional max-entries) "Parse current buffer backwards from point and return a list of prompts. From f1661105afe3b041fdf4e7aa8550cdbb6bad582d Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Tue, 17 Sep 2024 11:01:18 +0200 Subject: [PATCH 04/10] cody system directive --- README.org | 45 +++++++++++++++++++++++++++++++++++++++++++-- gptel-cody.el | 8 ++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/README.org b/README.org index 6fda7f0..267e9c6 100644 --- a/README.org +++ b/README.org @@ -59,7 +59,7 @@ You can create a Sourcegraph access token at https://sourcegraph.com/users/yourn (setopt cody-workspace-root "/your/path/to/some/project/dir") ; optional :config (defalias 'cody-start 'cody-login)) -#+end_src +#+end_src * Available commands @@ -89,8 +89,49 @@ Right now I think I am just hardcoding it at request time. As follow-up need to handle errors querying the backend to find out which models are allowed by the logged in user. -** TODO cody system prompts +** DONE cody system prompt +** TODO remote prompts + +#+begin_src graphql + query ViewerPrompts($query: String!) { + prompts(query: $query, first: 100, viewerIsAffiliated: true, orderBy: PROMPT_NAME_WITH_OWNER) { + nodes { + id + name + nameWithOwner + owner { + namespaceName + } + description + draft + definition { + text + } + url + } + totalCount + pageInfo { + hasNextPage + endCursor + } + } + } +#+end_src + +** TODO handle empty text + +I don't know if this is a bug in gptel or strictness on Cody's side. But I set +a directive without having inline prose, and we ended up having a final bit of +the transcript being + +#+begin_example + { + "speaker": "human", + "text": "" + } +#+end_example +This lead to a failure from the server. ** TODO make it possible to change instance (test against s2) ** TODO see if we can plug into same auth source Maybe also parse vscode state? Right now it is all in a sqlite file. diff --git a/gptel-cody.el b/gptel-cody.el index 52946a9..ca25cff 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -84,6 +84,13 @@ MAX-ENTRIES is the number of queries/responses to include for context." :text gptel--system-message) prompts))) +(cl-defun gptel-cody-add-directive () + "Add the Cody directive to the end of `gptel-directives` if missing." + (unless (assq 'cody gptel-directives) + (customize-set-variable 'gptel-directives + (append gptel-directives + '((cody . "You are Cody, an AI coding assistant from Sourcegraph. If your answer contains fenced code blocks in Markdown, include the relevant full file path in the code block tag using this structure: ```$LANGUAGE:$FILEPATH```")))))) + ;;;###autoload (cl-defun gptel-make-cody (name &key @@ -132,6 +139,7 @@ alist. KEY (optional) is a variable whose value is the API key, or function that returns the key." + (gptel-cody-add-directive) (declare (indent 1)) (let ((backend (gptel--make-cody :curl-args curl-args From 5d9e28117e070845b9322b388f36e2e9feca0524 Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Tue, 17 Sep 2024 11:23:52 +0200 Subject: [PATCH 05/10] set user agent --- gptel-cody.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gptel-cody.el b/gptel-cody.el index ca25cff..a9040e2 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -99,7 +99,7 @@ MAX-ENTRIES is the number of queries/responses to include for context." `(("Authorization" . ,(concat "token " key)) ("Content-Type" . "application/json") ("Accept-Encoding" . "gzip;q=0") - ("User-Agent" . "edit / v1"))))) + ("User-Agent" . "Cody-Emacs-gptel/0.0.1-dev"))))) (host "sourcegraph.com") (protocol "https") (endpoint "/.api/completions/stream") @@ -151,7 +151,7 @@ function that returns the key." :protocol protocol :endpoint endpoint :stream stream - :url (concat protocol "://" host endpoint "?api-version=2&client-name=gptel&client-version=v1")))) + :url (concat protocol "://" host endpoint "?api-version=2&client-name=Cody-Emacs-gptel&client-version=0.0.1-dev")))) (setf (alist-get name gptel--known-backends nil nil #'equal) backend))) From c39c8f1783991cd9a661a9d04d4800d32319842f Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Tue, 17 Sep 2024 11:27:53 +0200 Subject: [PATCH 06/10] gptel against s2 --- README.org | 14 +++++++++++--- gptel-cody.el | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/README.org b/README.org index 267e9c6..58c1e12 100644 --- a/README.org +++ b/README.org @@ -79,10 +79,18 @@ We have an experimental backend for [[https://github.com/karthink/gptel][gptel]] ;; Set Cody as the default backend (setq gptel-backend (gptel-make-cody "Cody" - :key "sgp_ADD_DOTCOM_TOKEN_HERE" - :stream t)) + :key "sgp_ADD_DOTCOM_TOKEN_HERE")) + + ;; Or use against enterprise instance + (setq gptel-backend + (gptel-make-cody "Cody" + :host "sourcegraph.sourcegraph.com" + :key "sgp_TOKEN_HERE")) #+end_src +You can have multiple cody accounts by giving the backends different names. IE +replace the word "Cody" with the name you want to refer to it by. + ** DONE ensure model selection works Right now I think I am just hardcoding it at request time. @@ -90,6 +98,7 @@ As follow-up need to handle errors querying the backend to find out which models are allowed by the logged in user. ** DONE cody system prompt +** DONE make it possible to change instance (test against s2) ** TODO remote prompts #+begin_src graphql @@ -132,7 +141,6 @@ the transcript being #+end_example This lead to a failure from the server. -** TODO make it possible to change instance (test against s2) ** TODO see if we can plug into same auth source Maybe also parse vscode state? Right now it is all in a sqlite file. ** TODO enhanced context for enterprise diff --git a/gptel-cody.el b/gptel-cody.el index a9040e2..52602a3 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -104,15 +104,17 @@ MAX-ENTRIES is the number of queries/responses to include for context." (protocol "https") (endpoint "/.api/completions/stream") (stream t) - (models '("anthropic/claude-3-5-sonnet-20240620" - "anthropic/claude-3-opus-20240229" - "openai/gpt-4o" - "google/gemini-1.5-pro" - "openai/cody-chat-preview-001" - "openai/cody-chat-preview-002" - "google/gemini-1.5-flash" - "anthropic/claude-3-haiku-20240307" - "fireworks/accounts/fireworks/models/mixtral-8x7b-instruct")) + (models (if (string= host "sourcegraph.com") + '("anthropic/claude-3-5-sonnet-20240620" + "anthropic/claude-3-opus-20240229" + "openai/gpt-4o" + "google/gemini-1.5-pro" + "openai/cody-chat-preview-001" + "openai/cody-chat-preview-002" + "google/gemini-1.5-flash" + "anthropic/claude-3-haiku-20240307" + "fireworks/accounts/fireworks/models/mixtral-8x7b-instruct") + '("anthropic::2023-06-01::claude-3.5-sonnet"))) (key 'gptel-api-key) curl-args) "Create a Cody API backend for gptel. From 4953f44d87e2b7ab5ab10f8c61d8325303e14fcd Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Tue, 17 Sep 2024 11:29:28 +0200 Subject: [PATCH 07/10] remove extra headers --- gptel-cody.el | 2 -- 1 file changed, 2 deletions(-) diff --git a/gptel-cody.el b/gptel-cody.el index 52602a3..65849d7 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -97,8 +97,6 @@ MAX-ENTRIES is the number of queries/responses to include for context." (header (lambda () (when-let (key (gptel--get-api-key)) `(("Authorization" . ,(concat "token " key)) - ("Content-Type" . "application/json") - ("Accept-Encoding" . "gzip;q=0") ("User-Agent" . "Cody-Emacs-gptel/0.0.1-dev"))))) (host "sourcegraph.com") (protocol "https") From 8941bb619a71db9b6d2971a6bcd034a4016118ce Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Wed, 18 Sep 2024 11:54:05 +0200 Subject: [PATCH 08/10] support fetching model list from server --- README.org | 20 +++++++++++++------- gptel-cody.el | 49 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/README.org b/README.org index 58c1e12..a41ed10 100644 --- a/README.org +++ b/README.org @@ -86,6 +86,12 @@ We have an experimental backend for [[https://github.com/karthink/gptel][gptel]] (gptel-make-cody "Cody" :host "sourcegraph.sourcegraph.com" :key "sgp_TOKEN_HERE")) + + ;; Specify models to avoid request against server to fetch list of models + (setq gptel-backend (gptel-make-cody "Cody" + :host "sourcegraph.sourcegraph.com" + :key "sgp_TOKEN_HERE" + :models '("anthropic::2023-06-01::claude-3.5-sonnet"))) #+end_src You can have multiple cody accounts by giving the backends different names. IE @@ -99,6 +105,13 @@ models are allowed by the logged in user. ** DONE cody system prompt ** DONE make it possible to change instance (test against s2) +** DONE model list from backend? +I think we have this sort of functionality, but I do see a lot of hardcoding +in the cody client. + +This should be possible, since I just tried with a random model on my free +account and got this response from the backend +: the requested chat model is not available ("openai/gpt-4o", onProTier=false, hasFeatureFlags=false) ** TODO remote prompts #+begin_src graphql @@ -146,10 +159,3 @@ Maybe also parse vscode state? Right now it is all in a sqlite file. ** TODO enhanced context for enterprise [[file:context-discussion.md][context-discussions.md]] ** TODO handle errors from backend -** TODO model list from backend? -I think we have this sort of functionality, but I do see a lot of hardcoding -in the cody client. - -This should be possible, since I just tried with a random model on my free -account and got this response from the backend -: the requested chat model is not available ("openai/gpt-4o", onProTier=false, hasFeatureFlags=false) diff --git a/gptel-cody.el b/gptel-cody.el index 65849d7..eac6fd8 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -28,6 +28,37 @@ (:copier nil) (:include gptel-backend))) +(cl-defmethod gptel-cody-fetch-models-async ((backend gptel-cody)) + "Asynchronously fetch models for the Cody backend and update the models slot." + (let* ((host (gptel-backend-host backend)) + (url (format "https://%s/.api/modelconfig/supported-models.json" host)) + (url-request-method "GET") + (url-request-extra-headers + (funcall (gptel-backend-header backend)))) + (url-retrieve + url + (lambda (status) + (if-let ((err (plist-get status :error))) + (message "Error fetching Cody models: %s" err) + (goto-char (point-min)) + (re-search-forward "\n\n") + (let* ((json-object-type 'alist) + (json-array-type 'list) + (json-key-type 'symbol) + (response (json-read)) + (default-chat-model (cdr (assoc 'chat (cdr (assoc 'defaultModels response))))) + (all-models (cdr (assoc 'models response))) + (chat-models (seq-filter + (lambda (model) + (and (member "chat" (cdr (assoc 'capabilities model))) + (not (string-match-p "fast-edit" (cdr (assoc 'modelRef model)))))) + all-models)) + (chat-models-names (mapcar (lambda (model) (cdr (assoc 'modelRef model))) chat-models)) + (chat-models-names (cons default-chat-model (remove default-chat-model chat-models-names)))) + (setf (gptel-cody-models backend) chat-models-names) + (message "Updated %s models: %s" (gptel-backend-name backend) chat-models-names)))) + nil t t))) + (cl-defmethod gptel-curl--parse-stream ((_backend gptel-cody) _info) "Parse Cody's streaming response." (let ((content-strs)) @@ -102,17 +133,7 @@ MAX-ENTRIES is the number of queries/responses to include for context." (protocol "https") (endpoint "/.api/completions/stream") (stream t) - (models (if (string= host "sourcegraph.com") - '("anthropic/claude-3-5-sonnet-20240620" - "anthropic/claude-3-opus-20240229" - "openai/gpt-4o" - "google/gemini-1.5-pro" - "openai/cody-chat-preview-001" - "openai/cody-chat-preview-002" - "google/gemini-1.5-flash" - "anthropic/claude-3-haiku-20240307" - "fireworks/accounts/fireworks/models/mixtral-8x7b-instruct") - '("anthropic::2023-06-01::claude-3.5-sonnet"))) + (models nil) (key 'gptel-api-key) curl-args) "Create a Cody API backend for gptel. @@ -147,11 +168,15 @@ function that returns the key." :host host :header header :key key - :models models + :models (or models (if (string= host "sourcegraph.com") + '("anthropic/claude-3-5-sonnet-20240620") + '("anthropic::2023-06-01::claude-3.5-sonnet"))) :protocol protocol :endpoint endpoint :stream stream :url (concat protocol "://" host endpoint "?api-version=2&client-name=Cody-Emacs-gptel&client-version=0.0.1-dev")))) + ;; using default models, so fetch from remote. + (unless models (gptel-cody-fetch-models-async backend)) (setf (alist-get name gptel--known-backends nil nil #'equal) backend))) From 6be3225dfdf072d981d40cc85d3aa07c8c7785e5 Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Thu, 19 Sep 2024 11:09:48 +0200 Subject: [PATCH 09/10] update header --- gptel-cody.el | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/gptel-cody.el b/gptel-cody.el index eac6fd8..a3643a6 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -3,27 +3,30 @@ ;; Copyright (C) 2024 Sourcegraph ;; Author: Keegan Carruthers-Smith +;; Version: 0.0.1-alpha +;; Package-Requires: ((emacs "27.1") (gptel "0.9.0")) +;; Keywords: convenience +;; URL: https://github.com/sourcegraph/gptel-cody -;; 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 . +;; SPDX-License-Identifier: Apache-2.0 ;;; Commentary: - ;; This file adds support for the Cody API to gptel ;;; Code: (require 'gptel) +(defconst gptel-cody--client-name "Cody-Emacs-gptel" + "The client name for Cody API requests.") + +(defconst gptel-cody--version + (package-get-version) + "The version of gptel-cody package.") + +(defconst gptel-cody--user-agent + (format "%s/%s" gptel-cody--client-name gptel-cody--version) + "The User-Agent string for Cody API requests.") + (cl-defstruct (gptel-cody (:constructor gptel--make-cody) (:copier nil) (:include gptel-backend))) @@ -128,7 +131,7 @@ MAX-ENTRIES is the number of queries/responses to include for context." (header (lambda () (when-let (key (gptel--get-api-key)) `(("Authorization" . ,(concat "token " key)) - ("User-Agent" . "Cody-Emacs-gptel/0.0.1-dev"))))) + ("User-Agent" . ,gptel-cody--user-agent))))) (host "sourcegraph.com") (protocol "https") (endpoint "/.api/completions/stream") @@ -174,7 +177,7 @@ function that returns the key." :protocol protocol :endpoint endpoint :stream stream - :url (concat protocol "://" host endpoint "?api-version=2&client-name=Cody-Emacs-gptel&client-version=0.0.1-dev")))) + :url (concat protocol "://" host endpoint "?api-version=2&client-name=" gptel-cody--client-name "&client-version=" gptel-cody--version)))) ;; using default models, so fetch from remote. (unless models (gptel-cody-fetch-models-async backend)) (setf (alist-get name gptel--known-backends From 159ddfe1b95ec0961b4ddec4615c2e3a6f085386 Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Thu, 19 Sep 2024 12:29:27 +0200 Subject: [PATCH 10/10] fix default header It needs to use the key argument as a default. This is a bug in upstream I need to fix, since it ends up only using the key for the default gptel-backend. --- gptel-cody.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gptel-cody.el b/gptel-cody.el index a3643a6..5de9547 100644 --- a/gptel-cody.el +++ b/gptel-cody.el @@ -128,16 +128,16 @@ MAX-ENTRIES is the number of queries/responses to include for context." ;;;###autoload (cl-defun gptel-make-cody (name &key - (header (lambda () - (when-let (key (gptel--get-api-key)) - `(("Authorization" . ,(concat "token " key)) - ("User-Agent" . ,gptel-cody--user-agent))))) (host "sourcegraph.com") (protocol "https") (endpoint "/.api/completions/stream") (stream t) (models nil) (key 'gptel-api-key) + (header (lambda () + (when-let (key-resolved (gptel--get-api-key key)) + `(("Authorization" . ,(concat "token " key-resolved)) + ("User-Agent" . ,gptel-cody--user-agent))))) curl-args) "Create a Cody API backend for gptel.