Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Chat
- `eca-chat-reset`: Close current chat buffer and start a new one (the chat stays on the server, resumable via `/resume`)
- `eca-chat-delete`: Delete the active chat from the server without prompting (works from any buffer in the project)
- `eca-chat-select-model`: Change chat model
- `eca-chat-refresh-models`: Refresh available models without restarting ECA. Also available from the chat menu and Providers tab.
- `eca-chat-select-agent`: Change chat agent
- `eca-chat-cycle-agent`: Change chat agent to next available
- `eca-chat-add-context-to-system-prompt`: Add file/dirs to system prompt checking multiple modes with range support
Expand Down
74 changes: 65 additions & 9 deletions eca-chat.el
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

(require 'eca-util)
(require 'eca-api)
(require 'eca-providers)
(require 'eca-mcp)
(require 'eca-diff)
(require 'eca-table)
Expand Down Expand Up @@ -2522,7 +2523,17 @@ without finalizing it)."
(let* ((prompt-contexts (eca-chat--extract-contexts-from-prompt))
(refined-contexts (->> (append eca-chat--context prompt-contexts)
(-map #'eca-chat--refine-context)
(-keep #'eca-chat--materialize-context))))
(-keep #'eca-chat--materialize-context)))
(model (eca-chat--model))
(stale-model (and (not eca-chat-custom-model)
model (seq-first (eca--session-models session))
(not (seq-contains-p (eca--session-models session)
model))))
(chat-buffer (current-buffer))
(variant (eca-chat--variant))
(default-model (eca--session-chat-default-model session))
(default-variant (eca--session-chat-default-variant session))
request-id)
(when eca-chat--welcome-shown (eca-chat--clear))
(add-to-list 'eca-chat--history prompt)
(setq eca-chat--history-index -1)
Expand All @@ -2532,20 +2543,37 @@ without finalizing it)."
session
:method "chat/prompt"
:params (append (list :message (eca-chat--normalize-prompt prompt)
:request-id (cl-incf eca-chat--last-request-id)
:request-id (setq request-id
(cl-incf eca-chat--last-request-id))
:chatId eca-chat--id
:model (eca-chat--model)
:model (unless stale-model model)
:agent (eca-chat--agent)
:contexts (vconcat refined-contexts))
(when-let* ((variant (eca-chat--variant)))
(when (and variant (not stale-model))
(list :variant variant))
(when (eca-chat--trust)
(list :trust t)))
;; The chat-id is already set buffer-locally at chat creation
;; time, so the prompt response carries no information we need
;; to act on. Pass `#'ignore' (rather than nil) so the response
;; dispatcher still removes the pending-handler entry.
:success-callback #'ignore)))
:success-callback
(if stale-model
(lambda (result)
(let ((resolved (plist-get result :model)))
(when (and (buffer-live-p chat-buffer)
resolved
(seq-contains-p (eca--session-models session) resolved))
(with-current-buffer chat-buffer
(when (and (= eca-chat--last-request-id request-id)
(equal (eca-chat--model) model))
(setq-local eca-chat--selected-model resolved)
(when (equal (eca-chat--variant) variant)
(setq-local eca-chat--selected-variant nil))
(when (equal (eca--session-chat-default-model session)
default-model)
(setf (eca--session-chat-default-model session) resolved)
(when (equal (eca--session-chat-default-variant session)
default-variant)
(setf (eca--session-chat-default-variant session) nil)))
(eca-chat--notify-status-changed session))))))
#'ignore))))

(defun eca-chat--queued-prompt-display-string (text)
"Return a display string for queued prompt TEXT, truncated to 40 chars."
Expand Down Expand Up @@ -5890,6 +5918,34 @@ When ACTIVE is non-nil, show the question prefix; otherwise restore normal."
:params (list :chatId eca-chat--id :messages t)))
(eca-chat--clear)))

;;;###autoload
(defun eca-chat-refresh-models ()
"Refresh the model catalog for the current ECA session."
(interactive)
(let ((session (eca-session)))
(eca-assert-session-running session)
(eca-info "Refreshing models...")
(eca-api-request-async session
:method "models/refresh"
:params nil
:success-callback
(lambda (result)
(eca-info "Model refresh complete: %d models"
(plist-get result :modelCount))
(seq-doseq (warning (plist-get result :warnings))
(eca-warn "%s: %s" (plist-get warning :provider)
(plist-get warning :message)))
(when (> (length (plist-get result :warnings)) 1)
(eca-info "Model refresh complete: %d models, %d warnings (see *Messages*)"
(plist-get result :modelCount)
(length (plist-get result :warnings))))
(when-let* ((buffer (eca-settings--get-buffer "providers" session))
((buffer-live-p buffer)))
(eca-providers--fetch-and-render session buffer)))
:error-callback
(lambda (err)
(eca-warn "Model refresh failed: %s" err)))))

;;;###autoload
(defun eca-chat-select-model ()
"Select which model to use in the active chat."
Expand Down
53 changes: 34 additions & 19 deletions eca-providers.el
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,12 @@ When all methods are just API key entry, returns nil."
(erase-buffer)
(insert "\n")
(insert (propertize "Providers / Models" 'font-lock-face 'eca-settings-heading))
(insert "\n")
(insert " "
(eca-buttonize
keymap
(propertize "Refresh models" 'font-lock-face 'eca-providers-button-face)
#'eca-chat-refresh-models)
"\n")
(insert (propertize "For more details check " 'face 'shadow))
(insert-text-button "https://eca.dev/config/models/"
'face 'link
Expand Down Expand Up @@ -485,26 +490,36 @@ When all methods are just API key entry, returns nil."

;; Data loading

(defvar-local eca-providers--list-request nil
"Latest provider list request for this tab buffer and session.")

(defun eca-providers--fetch-and-render (session buffer)
"Fetch provider list from SESSION and render into BUFFER."
(eca-api-request-async session
:method "providers/list"
:params nil
:success-callback
(lambda (result)
(setf (eca--session-providers session)
(append (plist-get result :providers) nil))
(when (buffer-live-p buffer)
(eca-providers--render session buffer)))
:error-callback
(lambda (err)
(when (buffer-live-p buffer)
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(insert "\n")
(insert (propertize "Failed to load providers" 'face 'error))
(insert (format "\n\n %s" err))))))))
(let ((request (cons session nil)))
(with-current-buffer buffer
(setq eca-providers--list-request request))
(eca-api-request-async session
:method "providers/list"
:params nil
:success-callback
(lambda (result)
(when (and (buffer-live-p buffer)
(eq (buffer-local-value 'eca-providers--list-request buffer)
request))
(setf (eca--session-providers session)
(append (plist-get result :providers) nil))
(eca-providers--render session buffer)))
:error-callback
(lambda (err)
(when (and (buffer-live-p buffer)
(eq (buffer-local-value 'eca-providers--list-request buffer)
request))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(insert "\n")
(insert (propertize "Failed to load providers" 'face 'error))
(insert (format "\n\n %s" err)))))))))

;; Settings tab

Expand Down
1 change: 1 addition & 0 deletions eca-util.el
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ Inheirits BASE-MAP."
("p" "Repeat prompt" eca-chat-repeat-prompt)
("C" "Clear prompt" eca-chat-clear-prompt)
("m" "Select model" eca-chat-select-model)
("F" "Refresh models" eca-chat-refresh-models)
("M" "Toggle MCP server" eca-mcp-toggle-server)
("v" "Select variant" eca-chat-select-variant)
("b" "Change agent" eca-chat-select-agent)
Expand Down
Loading
Loading