|
128 | 128 | m)) |
129 | 129 | (assoc-some {} :content-id content-id) |
130 | 130 | message-content) |
131 | | - image-entries (keep |
132 | | - (fn [content] |
133 | | - (when (= :image (:type content)) |
134 | | - {:role role |
135 | | - :content {:type :image |
136 | | - :media-type (:media-type content) |
137 | | - :base64 (:base64 content)}})) |
138 | | - message-content) |
| 131 | + image-entries (keep |
| 132 | + (fn [content] |
| 133 | + (when (= :image (:type content)) |
| 134 | + {:role role |
| 135 | + :content {:type :image |
| 136 | + :media-type (:media-type content) |
| 137 | + :base64 (:base64 content)}})) |
| 138 | + message-content) |
139 | 139 | ;; Drop the text entry when there's no actual text and no image-only content |
140 | 140 | ;; would have produced an empty `{}` content map. |
141 | | - text-entries (if (:type text-content) |
142 | | - [{:role role :content text-content}] |
143 | | - [])] |
144 | | - (vec (concat text-entries image-entries))) |
| 141 | + text-entries (if (:type text-content) |
| 142 | + [{:role role :content text-content}] |
| 143 | + [])] |
| 144 | + (vec (concat text-entries image-entries))) |
145 | 145 | "tool_call" [{:role :assistant |
146 | 146 | :content {:type :toolCallPrepare |
147 | 147 | :origin (:origin message-content) |
|
778 | 778 | (lifecycle/send-content! chat-ctx :assistant {:type :text :text (:text msg)})) |
779 | 779 | :url (lifecycle/send-content! chat-ctx :assistant {:type :url :title (:title msg) :url (:url msg)}) |
780 | 780 | :image (let [client-content {:type :image |
781 | | - :media-type (:media-type msg) |
782 | | - :base64 (:base64 msg)} |
| 781 | + :media-type (:media-type msg) |
| 782 | + :base64 (:base64 msg)} |
783 | 783 | history-content (assoc-some |
784 | 784 | {:media-type (:media-type msg) |
785 | 785 | :base64 (:base64 msg)} |
|
1333 | 1333 | :parent-chat-id (get-in @db* [:chats chat-id :parent-chat-id])) |
1334 | 1334 | _ (when (some? trust) |
1335 | 1335 | (swap! db* assoc-in [:chats chat-id :trust] trust))] |
1336 | | - (try |
1337 | | - (prompt* params base-chat-ctx) |
1338 | | - (catch Exception e |
1339 | | - (logger/error e) |
1340 | | - (lifecycle/send-content! base-chat-ctx :system {:type :text |
1341 | | - :text (str "Error: " (ex-message e) "\n\nCheck ECA stderr for more details.")}) |
1342 | | - (lifecycle/finish-chat-prompt! :idle (dissoc base-chat-ctx :on-finished-side-effect)) |
1343 | | - {:chat-id chat-id |
1344 | | - :model "error" |
1345 | | - :status :error})))))) |
| 1336 | + (logger/with-chat-context chat-id (:parent-chat-id base-chat-ctx) |
| 1337 | + (try |
| 1338 | + (prompt* params base-chat-ctx) |
| 1339 | + (catch Exception e |
| 1340 | + (logger/error e) |
| 1341 | + (lifecycle/send-content! base-chat-ctx :system {:type :text |
| 1342 | + :text (str "Error: " (ex-message e) "\n\nCheck ECA stderr for more details.")}) |
| 1343 | + (lifecycle/finish-chat-prompt! :idle (dissoc base-chat-ctx :on-finished-side-effect)) |
| 1344 | + {:chat-id chat-id |
| 1345 | + :model "error" |
| 1346 | + :status :error}))))))) |
1346 | 1347 |
|
1347 | 1348 | (defn tool-call-approve [{:keys [chat-id tool-call-id save]} db* messenger metrics] |
1348 | | - (if-not (get-in @db* [:chats chat-id :tool-calls tool-call-id]) |
1349 | | - (logger/warn logger-tag "tool-call-approve ignored: unknown chat or tool-call" |
1350 | | - {:chat-id chat-id :tool-call-id tool-call-id}) |
1351 | | - (let [chat-ctx {:chat-id chat-id |
1352 | | - :db* db* |
1353 | | - :metrics metrics |
1354 | | - :messenger messenger}] |
1355 | | - (tc/transition-tool-call! db* chat-ctx tool-call-id :user-approve |
1356 | | - {:reason {:code :user-choice-allow |
1357 | | - :text "Tool call allowed by user choice"}}) |
1358 | | - (when (= "session" save) |
1359 | | - (let [tool-call-name (get-in @db* [:chats chat-id :tool-calls tool-call-id :name])] |
1360 | | - (swap! db* assoc-in [:tool-calls tool-call-name :remember-to-approve?] true)))))) |
| 1349 | + (logger/with-chat-context chat-id (get-in @db* [:chats chat-id :parent-chat-id]) |
| 1350 | + (if-not (get-in @db* [:chats chat-id :tool-calls tool-call-id]) |
| 1351 | + (logger/warn logger-tag "tool-call-approve ignored: unknown chat or tool-call" |
| 1352 | + {:chat-id chat-id :tool-call-id tool-call-id}) |
| 1353 | + (let [chat-ctx {:chat-id chat-id |
| 1354 | + :db* db* |
| 1355 | + :metrics metrics |
| 1356 | + :messenger messenger}] |
| 1357 | + (tc/transition-tool-call! db* chat-ctx tool-call-id :user-approve |
| 1358 | + {:reason {:code :user-choice-allow |
| 1359 | + :text "Tool call allowed by user choice"}}) |
| 1360 | + (when (= "session" save) |
| 1361 | + (let [tool-call-name (get-in @db* [:chats chat-id :tool-calls tool-call-id :name])] |
| 1362 | + (swap! db* assoc-in [:tool-calls tool-call-name :remember-to-approve?] true))))))) |
1361 | 1363 |
|
1362 | 1364 | (defn tool-call-reject [{:keys [chat-id tool-call-id]} db* messenger metrics] |
1363 | | - (if-not (get-in @db* [:chats chat-id :tool-calls tool-call-id]) |
1364 | | - (logger/warn logger-tag "tool-call-reject ignored: unknown chat or tool-call" |
1365 | | - {:chat-id chat-id :tool-call-id tool-call-id}) |
1366 | | - (let [chat-ctx {:chat-id chat-id |
1367 | | - :db* db* |
1368 | | - :metrics metrics |
1369 | | - :messenger messenger}] |
1370 | | - (tc/transition-tool-call! db* chat-ctx tool-call-id :user-reject |
1371 | | - {:reason {:code :user-choice-deny |
1372 | | - :text "Tool call rejected by user choice"}})))) |
| 1365 | + (logger/with-chat-context chat-id (get-in @db* [:chats chat-id :parent-chat-id]) |
| 1366 | + (if-not (get-in @db* [:chats chat-id :tool-calls tool-call-id]) |
| 1367 | + (logger/warn logger-tag "tool-call-reject ignored: unknown chat or tool-call" |
| 1368 | + {:chat-id chat-id :tool-call-id tool-call-id}) |
| 1369 | + (let [chat-ctx {:chat-id chat-id |
| 1370 | + :db* db* |
| 1371 | + :metrics metrics |
| 1372 | + :messenger messenger}] |
| 1373 | + (tc/transition-tool-call! db* chat-ctx tool-call-id :user-reject |
| 1374 | + {:reason {:code :user-choice-deny |
| 1375 | + :text "Tool call rejected by user choice"}}))))) |
1373 | 1376 |
|
1374 | 1377 | (defn query-context |
1375 | 1378 | [{:keys [query contexts chat-id]} |
|
1402 | 1405 |
|
1403 | 1406 | (defn prompt-steer |
1404 | 1407 | [{:keys [chat-id message]} db*] |
1405 | | - (when (and (string? message) |
1406 | | - (not (string/blank? message)) |
1407 | | - (identical? :running (get-in @db* [:chats chat-id :status]))) |
1408 | | - (logger/info logger-tag "Steer message received" {:chat-id chat-id}) |
1409 | | - (swap! db* update-in [:chats chat-id :steer-message] |
1410 | | - (fn [existing] (if existing (str existing "\n" message) message))))) |
| 1408 | + (logger/with-chat-context chat-id (get-in @db* [:chats chat-id :parent-chat-id]) |
| 1409 | + (when (and (string? message) |
| 1410 | + (not (string/blank? message)) |
| 1411 | + (identical? :running (get-in @db* [:chats chat-id :status]))) |
| 1412 | + (logger/info logger-tag "Steer message received" {:chat-id chat-id}) |
| 1413 | + (swap! db* update-in [:chats chat-id :steer-message] |
| 1414 | + (fn [existing] (if existing (str existing "\n" message) message)))))) |
1411 | 1415 |
|
1412 | 1416 | (defn prompt-steer-remove |
1413 | 1417 | "Drop any pending steer message for the chat. |
1414 | 1418 | No-op if no steer message is pending or the chat is not present. |
1415 | 1419 | Idempotent: cancelling an already-consumed steer is silent." |
1416 | 1420 | [{:keys [chat-id]} db*] |
1417 | | - (let [removed?* (volatile! false)] |
1418 | | - (swap! db* (fn [db] |
1419 | | - (if (get-in db [:chats chat-id :steer-message]) |
1420 | | - (do (vreset! removed?* true) |
1421 | | - (update-in db [:chats chat-id] dissoc :steer-message)) |
1422 | | - db))) |
1423 | | - (when @removed?* |
1424 | | - (logger/info logger-tag "Steer message removed" {:chat-id chat-id})))) |
| 1421 | + (logger/with-chat-context chat-id (get-in @db* [:chats chat-id :parent-chat-id]) |
| 1422 | + (let [removed?* (volatile! false)] |
| 1423 | + (swap! db* (fn [db] |
| 1424 | + (if (get-in db [:chats chat-id :steer-message]) |
| 1425 | + (do (vreset! removed?* true) |
| 1426 | + (update-in db [:chats chat-id] dissoc :steer-message)) |
| 1427 | + db))) |
| 1428 | + (when @removed?* |
| 1429 | + (logger/info logger-tag "Steer message removed" {:chat-id chat-id}))))) |
1425 | 1430 |
|
1426 | 1431 | (defn prompt-stop |
1427 | 1432 | ([params db* messenger metrics] |
1428 | 1433 | (prompt-stop params db* messenger metrics {})) |
1429 | 1434 | ([{:keys [chat-id]} db* messenger metrics {:keys [silent?]}] |
1430 | | - (when (identical? :running (get-in @db* [:chats chat-id :status])) |
1431 | | - ;; Set :stopping immediately to prevent race with stream callbacks |
1432 | | - ;; that check status via assert-chat-not-stopped! or cancelled? |
1433 | | - (swap! db* assoc-in [:chats chat-id :status] :stopping) |
1434 | | - (let [chat-ctx {:chat-id chat-id |
1435 | | - :db* db* |
1436 | | - :metrics metrics |
1437 | | - :messenger messenger |
1438 | | - :parent-chat-id (get-in @db* [:chats chat-id :parent-chat-id])}] |
1439 | | - (when-not silent? |
1440 | | - (lifecycle/send-content! chat-ctx :system {:type :text |
1441 | | - :text "\nPrompt stopped"})) |
1442 | | - |
1443 | | - ;; Handle each active tool call |
1444 | | - (doseq [[tool-call-id _] (tc/get-active-tool-calls @db* chat-id)] |
1445 | | - (tc/transition-tool-call! db* chat-ctx tool-call-id :stop-requested |
1446 | | - {:reason {:code :user-prompt-stop |
1447 | | - :text "Tool call rejected because of user prompt stop"}})) |
1448 | | - ;; Clear compacting flags so finish-chat-prompt! isn't blocked |
1449 | | - (swap! db* update-in [:chats chat-id] dissoc :auto-compacting? :compacting?) |
1450 | | - (lifecycle/finish-chat-prompt! :stopping (dissoc chat-ctx :on-finished-side-effect)))))) |
| 1435 | + (logger/with-chat-context chat-id (get-in @db* [:chats chat-id :parent-chat-id]) |
| 1436 | + (when (identical? :running (get-in @db* [:chats chat-id :status])) |
| 1437 | + ;; Set :stopping immediately to prevent race with stream callbacks |
| 1438 | + ;; that check status via assert-chat-not-stopped! or cancelled? |
| 1439 | + (swap! db* assoc-in [:chats chat-id :status] :stopping) |
| 1440 | + (let [chat-ctx {:chat-id chat-id |
| 1441 | + :db* db* |
| 1442 | + :metrics metrics |
| 1443 | + :messenger messenger |
| 1444 | + :parent-chat-id (get-in @db* [:chats chat-id :parent-chat-id])}] |
| 1445 | + (when-not silent? |
| 1446 | + (lifecycle/send-content! chat-ctx :system {:type :text |
| 1447 | + :text "\nPrompt stopped"})) |
| 1448 | + |
| 1449 | + ;; Handle each active tool call |
| 1450 | + (doseq [[tool-call-id _] (tc/get-active-tool-calls @db* chat-id)] |
| 1451 | + (tc/transition-tool-call! db* chat-ctx tool-call-id :stop-requested |
| 1452 | + {:reason {:code :user-prompt-stop |
| 1453 | + :text "Tool call rejected because of user prompt stop"}})) |
| 1454 | + ;; Clear compacting flags so finish-chat-prompt! isn't blocked |
| 1455 | + (swap! db* update-in [:chats chat-id] dissoc :auto-compacting? :compacting?) |
| 1456 | + (lifecycle/finish-chat-prompt! :stopping (dissoc chat-ctx :on-finished-side-effect))))))) |
1451 | 1457 |
|
1452 | 1458 | (defn delete-chat |
1453 | 1459 | [{:keys [chat-id]} db* messenger config metrics] |
|
0 commit comments