Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Thank you for your interest in contributing to Efrit! This guide will help you g

2. **Set up your API key** in `~/.authinfo`:
```
machine api.anthropic.com login personal password YOUR_API_KEY_HERE
machine openrouter.ai login personal password YOUR_API_KEY_HERE
```

3. **Load Efrit for development**:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Efrit provides multiple interfaces for AI-powered Emacs development:

3. **Configure your API key** in `~/.authinfo`:
```
machine api.anthropic.com login personal password YOUR_API_KEY_HERE
machine openrouter.ai login personal password YOUR_API_KEY_HERE
```

4. **Restart Emacs** and test with `M-x efrit-chat`
Expand Down Expand Up @@ -218,7 +218,7 @@ Transform Efrit from a user assistant into an **autonomous AI development platfo

```elisp
;; Standard Efrit settings
(setq efrit-model "claude-3-5-sonnet-20241022")
(setq efrit-model "anthropic/claude-sonnet-4")
(setq efrit-max-tokens 8192)

;; 🆕 Agent communication settings
Expand Down
14 changes: 7 additions & 7 deletions lisp/efrit-agent.el
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
:group 'efrit
:prefix "efrit-agent-")

(defcustom efrit-agent-backend "claude-3.5-sonnet"
(defcustom efrit-agent-backend "anthropic/claude-sonnet-4"
"Default model backend for agent mode."
:type '(choice (const "claude-3.5-sonnet")
(const "gpt-4")
:type '(choice (const "anthropic/claude-sonnet-4")
(const "gpt-4")
(const "local-llama")
(string :tag "Custom API endpoint"))
:group 'efrit-agent)
Expand All @@ -44,12 +44,12 @@
:type 'integer
:group 'efrit-agent)

(defcustom efrit-agent-api-url "https://api.anthropic.com/v1/messages"
(defcustom efrit-agent-api-url "https://openrouter.ai/api/v1/chat/completions"
"API URL for Claude requests."
:type 'string
:group 'efrit-agent)

(defcustom efrit-agent-model "claude-4-sonnet-20250514"
(defcustom efrit-agent-model "anthropic/claude-sonnet-4"
"Model to use for agent requests. Updated to latest Claude 4 Sonnet."
:type 'string
:group 'efrit-agent)
Expand Down Expand Up @@ -166,7 +166,7 @@
(defun efrit-agent--get-api-key ()
"Get the Anthropic API key from .authinfo file."
(efrit-agent--log "DEBUG" "Looking for API key in .authinfo")
(let* ((auth-info (car (auth-source-search :host "api.anthropic.com"
(let* ((auth-info (car (auth-source-search :host "openrouter.ai"
:user "personal"
:require '(:secret))))
(secret (plist-get auth-info :secret)))
Expand All @@ -176,7 +176,7 @@
(if (functionp secret)
(funcall secret)
secret))
(efrit-agent--log "ERROR" "No API key found in .authinfo for api.anthropic.com")
(efrit-agent--log "ERROR" "No API key found in .authinfo for openrouter.ai")
nil)))

(defun efrit-agent--build-system-prompt ()
Expand Down
4 changes: 2 additions & 2 deletions lisp/efrit-chat-streamlined.el
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
:group 'tools
:prefix "efrit-")

(defcustom efrit-model "claude-3-5-sonnet-20241022"
(defcustom efrit-model "anthropic/claude-sonnet-4"
"Claude model to use for conversations."
:type 'string
:group 'efrit)
Expand All @@ -55,7 +55,7 @@
:type 'float
:group 'efrit)

(defcustom efrit-api-url "https://api.anthropic.com/v1/messages"
(defcustom efrit-api-url "https://openrouter.ai/api/v1/chat/completions"
"URL for the Anthropic API endpoint."
:type 'string
:group 'efrit)
Expand Down
10 changes: 4 additions & 6 deletions lisp/efrit-chat.el
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
:type 'string
:group 'efrit)

(defcustom efrit-model "claude-3-5-sonnet-20241022"
(defcustom efrit-model "anthropic/claude-sonnet-4"
"Claude model to use for conversations."
:type 'string
:group 'efrit)
Expand All @@ -63,7 +63,7 @@ or 4096 without. This setting uses the higher limit."
:type 'float
:group 'efrit)

(defcustom efrit-api-url "https://api.anthropic.com/v1/messages"
(defcustom efrit-api-url "https://openrouter.ai/api/v1/chat/completions"
"URL for the Anthropic API endpoint."
:type 'string
:group 'efrit)
Expand Down Expand Up @@ -126,7 +126,7 @@ or 4096 without. This setting uses the higher limit."

(defun efrit--get-api-key ()
"Get the Anthropic API key from .authinfo file."
(let* ((auth-info (car (auth-source-search :host "api.anthropic.com"
(let* ((auth-info (car (auth-source-search :host "openrouter.ai"
:user "personal"
:require '(:secret))))
(secret (plist-get auth-info :secret)))
Expand Down Expand Up @@ -228,9 +228,7 @@ or 4096 without. This setting uses the higher limit."
(let* ((api-key (efrit--get-api-key))
(url-request-method "POST")
(url-request-extra-headers
`(("x-api-key" . ,api-key)
("anthropic-version" . "2023-06-01")
("anthropic-beta" . "max-tokens-3-5-sonnet-2024-07-15")
`(("Authorization" . ,(concat "Bearer " openrouter-api-key))
("content-type" . "application/json")))
(system-prompt (when efrit-enable-tools (efrit-tools-system-prompt)))
(request-data
Expand Down
131 changes: 82 additions & 49 deletions lisp/efrit-do.el
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ If nil, uses the default location in the efrit data directory."
:type 'boolean
:group 'efrit-do)

(defcustom efrit-model "claude-3-5-sonnet-20241022"
(defcustom efrit-model "anthropic/claude-sonnet-4"
"Claude model to use for efrit-do commands."
:type 'string
:group 'efrit-do)
Expand All @@ -94,7 +94,7 @@ If nil, uses the default location in the efrit data directory."
:type 'integer
:group 'efrit-do)

(defcustom efrit-api-url "https://api.anthropic.com/v1/messages"
(defcustom efrit-api-url "https://openrouter.ai/api/v1/chat/completions"
"URL for the Anthropic API endpoint used by efrit-do."
:type 'string
:group 'efrit-do)
Expand Down Expand Up @@ -710,92 +710,102 @@ When `efrit-do-show-errors-only' is non-nil, only show buffer for errors."
display-buffer-below-selected
(window-height . 10)))))))


(defun efrit-do--execute-command (command &optional retry-count error-msg previous-code)
"Execute natural language COMMAND and return the result.
Uses improved error handling. If RETRY-COUNT is provided, this is a retry
Uses improved error handling. If RETRY-COUNT is provided, this is a retry
attempt with ERROR-MSG and PREVIOUS-CODE from the failed attempt."
(condition-case api-err
(let* ((api-key (efrit--get-api-key))
(url-request-method "POST")
(url-request-extra-headers
`(("x-api-key" . ,api-key)
("anthropic-version" . "2023-06-01")
`(("Authorization" . ,(concat "Bearer " openrouter-api-key))
("content-type" . "application/json")))
(system-prompt (efrit-do--command-system-prompt retry-count error-msg previous-code))
(request-data
`(("model" . ,efrit-model)
("max_tokens" . ,efrit-max-tokens)
("temperature" . 0.0)
("messages" . [(("role" . "user")
("messages" . [(("role" . "system")
("content" . ,system-prompt))
(("role" . "user")
("content" . ,command))])
("system" . ,system-prompt)
("tools" . [(("name" . "eval_sexp")
("tools" . [(("type" . "function")
("function" . (("name" . "eval_sexp")
("description" . "Evaluate a Lisp expression and return the result. This is the primary tool for interacting with Emacs.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("expr" . (("type" . "string")
("description" . "The Elisp expression to evaluate")))))
("required" . ["expr"]))))
(("name" . "shell_exec")
("required" . ["expr"]))))))
(("type" . "function")
("function" . (("name" . "shell_exec")
("description" . "Execute a shell command and return the result.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("command" . (("type" . "string")
("description" . "The shell command to execute")))))
("required" . ["command"]))))
(("name" . "todo_add")
("required" . ["command"]))))))
(("type" . "function")
("function" . (("name" . "todo_add")
("description" . "Add a new TODO item to track progress.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("content" . (("type" . "string")
("description" . "The TODO item description")))
("priority" . (("type" . "string")
("enum" . ["low" "medium" "high"])
("description" . "Priority level")))))
("required" . ["content"]))))
(("name" . "todo_update")
("required" . ["content"]))))))
(("type" . "function")
("function" . (("name" . "todo_update")
("description" . "Update the status of a TODO item.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("id" . (("type" . "string")
("description" . "The TODO item ID")))
("status" . (("type" . "string")
("enum" . ["todo" "in-progress" "completed"])
("description" . "New status")))))
("required" . ["id" "status"]))))
(("name" . "todo_show")
("required" . ["id" "status"]))))))
(("type" . "function")
("function" . (("name" . "todo_show")
("description" . "Show all current TODO items.")
("input_schema" . (("type" . "object")
("properties" . ()))))
(("name" . "buffer_create")
("parameters" . (("type" . "object")
("properties" . ()))))))
(("type" . "function")
("function" . (("name" . "buffer_create")
("description" . "Create a new buffer with content and optional mode. Use this for reports, lists, and formatted output.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("name" . (("type" . "string")
("description" . "Buffer name (e.g. '*efrit-report: Files*')")))
("content" . (("type" . "string")
("description" . "Buffer content")))
("mode" . (("type" . "string")
("description" . "Optional major mode (e.g. 'markdown-mode', 'org-mode')")))))
("required" . ["name" "content"]))))
(("name" . "format_file_list")
("required" . ["name" "content"]))))))
(("type" . "function")
("function" . (("name" . "format_file_list")
("description" . "Format content as a markdown file list with bullet points.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("content" . (("type" . "string")
("description" . "Raw content to format as file list")))))
("required" . ["content"]))))
(("name" . "format_todo_list")
("required" . ["content"]))))))
(("type" . "function")
("function" . (("name" . "format_todo_list")
("description" . "Format TODO list with optional sorting.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("sort_by" . (("type" . "string")
("enum" . ["status" "priority"])
("description" . "Optional sorting criteria")))))
("required" . []))))
(("name" . "display_in_buffer")
("description" . "Sorting criteria")))))
("required" . []))))))
(("type" . "function")
("function" . (("name" . "display_in_buffer")
("description" . "Display content in a specific buffer.")
("input_schema" . (("type" . "object")
("parameters" . (("type" . "object")
("properties" . (("buffer_name" . (("type" . "string")
("description" . "Buffer name")))
("content" . (("type" . "string")
("description" . "Content to display")))
("window_height" . (("type" . "number")
("description" . "Optional window height")))))
("required" . ["buffer_name" "content"]))))])))
("required" . ["buffer_name" "content"]))))))])))
(url-request-data
(encode-coding-string (json-encode request-data) 'utf-8)))

Expand Down Expand Up @@ -825,26 +835,49 @@ attempt with ERROR-MSG and PREVIOUS-CODE from the failed attempt."
(error-message (gethash "message" error-obj)))
(format "API Error (%s): %s" error-type error-message))

;; Process successful response
(let ((content (gethash "content" response)))
;; Process successful response - OpenRouter format
(let* ((choices (gethash "choices" response))
(first-choice (when (and choices (> (length choices) 0))
(aref choices 0)))
(message-obj (when first-choice
(gethash "message" first-choice)))
(content (when message-obj
(gethash "content" message-obj)))
(tool-calls (when message-obj
(gethash "tool_calls" message-obj))))

(when efrit-do-debug
(message "API Response content: %S" content))
(message "API Response content: %S" content)
(message "Tool calls: %S" tool-calls))

(when content
(dotimes (i (length content))
(let* ((item (aref content i))
(type (gethash "type" item)))
(cond
;; Handle text content
((string= type "text")
(when-let* ((text (gethash "text" item)))
(setq message-text (concat message-text text))))
(setq message-text (concat message-text content)))

(when tool-calls
(dotimes (i (length tool-calls))
(let* ((tool-call (aref tool-calls i))
(function-obj (gethash "function" tool-call))
(tool-name (gethash "name" function-obj))
(arguments-str (gethash "arguments" function-obj))
(arguments (when arguments-str
(condition-case err
(json-read-from-string arguments-str)
(error
(when efrit-do-debug
(message "Failed to parse tool arguments: %s" (error-message-string err)))
nil))))
(tool-item (make-hash-table :test 'equal)))

;; Create tool item in expected format
(puthash "name" tool-name tool-item)
(puthash "input" arguments tool-item)

(when efrit-do-debug
(message "Processing tool call: %s with args: %S" tool-name arguments))

;; Handle tool use
((string= type "tool_use")
(setq message-text
(concat message-text
(efrit-do--execute-tool item))))))))
(efrit-do--execute-tool tool-item))))))

(or message-text "Command executed"))))
(error
Expand Down
4 changes: 2 additions & 2 deletions lisp/efrit-do.el.backup
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ If nil, uses the default location in the efrit data directory."
:type 'boolean
:group 'efrit-do)

(defcustom efrit-model "claude-3-5-sonnet-20241022"
(defcustom efrit-model "anthropic/claude-sonnet-4"
"Claude model to use for efrit-do commands."
:type 'string
:group 'efrit-do)
Expand All @@ -94,7 +94,7 @@ If nil, uses the default location in the efrit data directory."
:type 'integer
:group 'efrit-do)

(defcustom efrit-api-url "https://api.anthropic.com/v1/messages"
(defcustom efrit-api-url "https://openrouter.ai/api/v1/chat/completions"
"URL for the Anthropic API endpoint used by efrit-do."
:type 'string
:group 'efrit-do)
Expand Down
Loading