|
4 | 4 | [clojure.java.io :as io] |
5 | 5 | [clojure.set :as set] |
6 | 6 | [clojure.string :as string] |
| 7 | + [eca.cache :as cache] |
7 | 8 | [eca.config :as config] |
8 | 9 | [eca.db :as db] |
| 10 | + [eca.digest :as digest] |
9 | 11 | [eca.features.background-tasks :as bg] |
10 | 12 | [eca.features.chat.history :as history] |
11 | 13 | [eca.features.chat.lifecycle :as lifecycle] |
|
64 | 66 | (str (System/getProperty "user.name") "@ECA" |
65 | 67 | (when (not-empty agent) (str "/" agent)))) |
66 | 68 |
|
| 69 | +(defn ^:private static-prompt-cache-signature |
| 70 | + "SHA-256 cache identity for static prompt reuse." |
| 71 | + [refined-contexts static-rules path-scoped-rules skills agent config chat-id all-tools db] |
| 72 | + (let [static-contexts (vec (filter f.prompt/static-prompt-context? refined-contexts))] |
| 73 | + (digest/sha-256-hex |
| 74 | + (pr-str |
| 75 | + {:agent agent |
| 76 | + :chat-prompt-template (f.prompt/eca-chat-prompt agent config chat-id db) |
| 77 | + :workspace-roots (mapv (comp shared/uri->filename :uri) (:workspace-folders db)) |
| 78 | + :environment {:os-name (str (System/getProperty "os.name") " " (System/getProperty "os.version")) |
| 79 | + :shell (or (System/getenv "SHELL") (System/getenv "ComSpec")) |
| 80 | + :user-name (System/getProperty "user.name") |
| 81 | + :home-dir (cache/user-home)} |
| 82 | + :is-subagent (boolean (get-in db [:chats chat-id :subagent])) |
| 83 | + :startup-context (get-in db [:chats chat-id :startup-context]) |
| 84 | + :static-contexts static-contexts |
| 85 | + :repo-map (when (some #(= :repoMap (:type %)) static-contexts) |
| 86 | + :present) |
| 87 | + :static-rules (mapv #(select-keys % [:id :name :scope :content]) static-rules) |
| 88 | + :path-scoped-rules (mapv #(select-keys % [:id :name :scope :workspace-root :paths :enforce]) path-scoped-rules) |
| 89 | + :skills (mapv #(select-keys % [:name :description]) skills) |
| 90 | + :tools (sort (map :full-name all-tools))})))) |
| 91 | + |
67 | 92 | (defn ^:private prune-tool-results! |
68 | 93 | "Prunes old tool result content from chat history to reduce context size. |
69 | 94 | Walks messages backwards, protecting the most recent tool outputs up to |
|
1451 | 1476 | agent))))) |
1452 | 1477 | repo-map* (delay (f.index/repo-map db config {:as-string? true})) |
1453 | 1478 | prompt-cache (get-in db [:chats chat-id :prompt-cache]) |
| 1479 | + static-signature (static-prompt-cache-signature |
| 1480 | + refined-contexts static-rules path-scoped-rules skills |
| 1481 | + agent config chat-id all-tools db) |
1454 | 1482 | instructions (if (and prompt-cache |
1455 | 1483 | (= (:agent prompt-cache) agent) |
1456 | | - (= (:model prompt-cache) full-model)) |
| 1484 | + (= (:model prompt-cache) full-model) |
| 1485 | + (= (:static-signature prompt-cache) static-signature)) |
1457 | 1486 | {:static (:static prompt-cache) |
1458 | 1487 | :dynamic (f.prompt/build-dynamic-instructions refined-contexts db)} |
1459 | 1488 | (let [result (f.prompt/build-chat-instructions |
1460 | 1489 | refined-contexts static-rules path-scoped-rules skills repo-map* |
1461 | 1490 | agent config chat-id all-tools db)] |
1462 | 1491 | (swap! db* assoc-in [:chats chat-id :prompt-cache] |
1463 | 1492 | {:static (:static result) |
| 1493 | + :static-signature static-signature |
1464 | 1494 | :agent agent |
1465 | 1495 | :model full-model}) |
1466 | 1496 | result)) |
|
1470 | 1500 | seq |
1471 | 1501 | (f.prompt/contexts-str repo-map* nil))] |
1472 | 1502 | [{:type :text :text contexts-str}]) |
| 1503 | + decision (message->decision message db config) |
1473 | 1504 | ;; Cursor (and other volatile editor-state) is delivered per-turn in the |
1474 | 1505 | ;; user message - never the system prompt - and only re-sent when it |
1475 | 1506 | ;; changed. This keeps the cached system/prefix stable across turns, |
1476 | 1507 | ;; avoiding llama.cpp full prompt re-processing on every cursor move. #464 |
| 1508 | + ;; Only normal prompts send this synthesized user message to the model; |
| 1509 | + ;; /prompt-show and MCP prompts use different messages. |
1477 | 1510 | editor-state-context (f.prompt/build-editor-state-context refined-contexts) |
1478 | | - editor-state-contents (when (and editor-state-context |
1479 | | - (not= editor-state-context |
1480 | | - (get-in db [:chats chat-id :last-editor-state]))) |
1481 | | - (swap! db* assoc-in [:chats chat-id :last-editor-state] editor-state-context) |
| 1511 | + editor-state-changed? (and editor-state-context |
| 1512 | + (not= editor-state-context |
| 1513 | + (get-in db [:chats chat-id :last-editor-state]))) |
| 1514 | + _ (when (and editor-state-changed? |
| 1515 | + (= :prompt-message (:type decision))) |
| 1516 | + (swap! db* assoc-in [:chats chat-id :last-editor-state] editor-state-context)) |
| 1517 | + editor-state-contents (when editor-state-changed? |
1482 | 1518 | [{:type :text :text editor-state-context}]) |
1483 | 1519 | user-messages [{:role "user" :content (vec (concat [{:type :text :text message}] |
1484 | 1520 | expanded-prompt-contexts |
|
1492 | 1528 | :full-model full-model |
1493 | 1529 | :provider provider |
1494 | 1530 | :model model |
1495 | | - :messenger messenger}) |
1496 | | - decision (message->decision message db config)] |
| 1531 | + :messenger messenger})] |
1497 | 1532 | ;; Show original prompt to user, but LLM receives the modified version |
1498 | 1533 | (lifecycle/send-content! chat-ctx :user {:type :text |
1499 | 1534 | :content-id (:user-content-id chat-ctx) |
|
0 commit comments