Skip to content

Commit 035457e

Browse files
committed
Refactor chat namespace splitting some code to be better to maintain
1 parent d9c6b09 commit 035457e

6 files changed

Lines changed: 1606 additions & 1561 deletions

File tree

src/eca/features/chat.clj

Lines changed: 300 additions & 1210 deletions
Large diffs are not rendered by default.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
(ns eca.features.chat.lifecycle
2+
(:require
3+
[eca.db :as db]
4+
[eca.features.hooks :as f.hooks]
5+
[eca.features.login :as f.login]
6+
[eca.logger :as logger]
7+
[eca.messenger :as messenger]
8+
[eca.shared :as shared :refer [assoc-some]]))
9+
10+
(set! *warn-on-reflection* true)
11+
12+
(def ^:private logger-tag "[CHAT]")
13+
14+
(defn new-content-id []
15+
(str (random-uuid)))
16+
17+
(defn auto-compact? [chat-id agent-name full-model config db]
18+
(when (and (not (get-in db [:chats chat-id :compacting?]))
19+
(not (get-in db [:chats chat-id :auto-compacting?])))
20+
(let [compact-threshold (or (get-in config [:agent agent-name :autoCompactPercentage])
21+
(get-in config [:autoCompactPercentage]))
22+
{:keys [session-tokens limit]} (shared/usage-sumary chat-id full-model db)]
23+
(when (and compact-threshold session-tokens (:context limit))
24+
(let [current-percentage (* (/ session-tokens (:context limit)) 100)]
25+
(>= current-percentage compact-threshold))))))
26+
27+
(defn send-content! [{:keys [messenger chat-id parent-chat-id]} role content]
28+
(messenger/chat-content-received
29+
messenger
30+
(assoc-some {:chat-id chat-id
31+
:role role
32+
:content content}
33+
:parent-chat-id parent-chat-id)))
34+
35+
(defn- format-hook-output
36+
"Format hook output for display, showing parsed JSON fields or raw output."
37+
[{:keys [systemMessage replacedPrompt additionalContext] :as parsed} raw-output]
38+
(if parsed
39+
(cond-> (or systemMessage "Hook executed")
40+
replacedPrompt (str "\nReplacedPrompt: " (pr-str replacedPrompt))
41+
additionalContext (str "\nAdditionalContext: " additionalContext))
42+
raw-output))
43+
44+
(defn notify-before-hook-action! [chat-ctx {:keys [id name type visible?]}]
45+
(when visible?
46+
(send-content! chat-ctx :system
47+
{:type :hookActionStarted
48+
:action-type type
49+
:name name
50+
:id id})))
51+
52+
(defn notify-after-hook-action! [chat-ctx {:keys [id name parsed raw-output raw-error exit type visible?]}]
53+
(when (and visible? (not (:suppressOutput parsed)))
54+
(send-content! chat-ctx :system
55+
{:type :hookActionFinished
56+
:action-type type
57+
:id id
58+
:name name
59+
:status exit
60+
:output (format-hook-output parsed raw-output)
61+
:error raw-error})))
62+
63+
(defn wrap-additional-context
64+
"Return XML-wrapped additional context attributed to `from`."
65+
[from content]
66+
(format "<additionalContext from=\"%s\">\n%s\n</additionalContext>"
67+
(name from)
68+
content))
69+
70+
(defn finish-chat-prompt! [status {:keys [message chat-id db* metrics config on-finished-side-effect prompt-id] :as chat-ctx}]
71+
(when-not (and prompt-id (not= prompt-id (get-in @db* [:chats chat-id :prompt-id])))
72+
(when-not (get-in @db* [:chats chat-id :auto-compacting?])
73+
(swap! db* assoc-in [:chats chat-id :status] status)
74+
(let [db @db*
75+
subagent? (some? (get-in db [:chats chat-id :subagent]))
76+
hook-type (if subagent? :subagentPostRequest :postRequest)
77+
hook-data (cond-> (merge (f.hooks/chat-hook-data db chat-id (:agent chat-ctx))
78+
{:prompt message})
79+
subagent? (assoc :parent-chat-id (get-in db [:chats chat-id :parent-chat-id])))]
80+
(f.hooks/trigger-if-matches! hook-type
81+
hook-data
82+
{:on-before-action (partial notify-before-hook-action! chat-ctx)
83+
:on-after-action (partial notify-after-hook-action! chat-ctx)}
84+
db
85+
config))
86+
(send-content! chat-ctx :system
87+
{:type :progress
88+
:state :finished})
89+
(when-not (get-in @db* [:chats chat-id :created-at])
90+
(swap! db* assoc-in [:chats chat-id :created-at] (System/currentTimeMillis))))
91+
(when on-finished-side-effect
92+
(on-finished-side-effect))
93+
(db/update-workspaces-cache! @db* metrics)))
94+
95+
(defn maybe-renew-auth-token [chat-ctx]
96+
(f.login/maybe-renew-auth-token!
97+
{:provider (:provider chat-ctx)
98+
:on-renewing (fn []
99+
(send-content! chat-ctx :system {:type :progress
100+
:state :running
101+
:text "Renewing auth token"}))
102+
:on-error (fn [error-msg]
103+
(send-content! chat-ctx :system {:type :text :text error-msg})
104+
(finish-chat-prompt! :idle (dissoc chat-ctx :on-finished-side-effect))
105+
(throw (ex-info "Auth token renew failed" {})))}
106+
chat-ctx))
107+
108+
(defn assert-chat-not-stopped! [{:keys [chat-id db* prompt-id] :as chat-ctx}]
109+
(let [chat (get-in @db* [:chats chat-id])
110+
superseded? (and prompt-id (not= prompt-id (:prompt-id chat)))
111+
stopped? (or (identical? :stopping (:status chat)) superseded?)]
112+
(when stopped?
113+
(finish-chat-prompt! :idle (dissoc chat-ctx :on-finished-side-effect))
114+
(logger/info logger-tag "Chat prompt stopped:" chat-id (when superseded? "(superseded)"))
115+
(throw (ex-info "Chat prompt stopped" {:silent? true
116+
:chat-id chat-id})))))

0 commit comments

Comments
 (0)