|
119 | 119 | (case role |
120 | 120 | ("user" |
121 | 121 | "system" |
122 | | - "assistant") [{:role role |
123 | | - :content (reduce |
124 | | - (fn [m content] |
125 | | - (case (:type content) |
126 | | - :text (assoc m |
127 | | - :type :text |
128 | | - :text (str (:text m) "\n" (:text content))) |
129 | | - m)) |
130 | | - (assoc-some {} :content-id content-id) |
131 | | - message-content)}] |
| 122 | + "assistant") (let [text-content (reduce |
| 123 | + (fn [m content] |
| 124 | + (case (:type content) |
| 125 | + :text (assoc m |
| 126 | + :type :text |
| 127 | + :text (str (:text m) "\n" (:text content))) |
| 128 | + m)) |
| 129 | + (assoc-some {} :content-id content-id) |
| 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) |
| 139 | + ;; Drop the text entry when there's no actual text and no image-only content |
| 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))) |
132 | 145 | "tool_call" [{:role :assistant |
133 | 146 | :content {:type :toolCallPrepare |
134 | 147 | :origin (:origin message-content) |
|
170 | 183 | :error (:error message-content) |
171 | 184 | :id (:id message-content) |
172 | 185 | :outputs (:contents (:output message-content))}}] |
| 186 | + "image_generation_call" [{:role :assistant |
| 187 | + :content {:type :image |
| 188 | + :media-type (:media-type message-content) |
| 189 | + :base64 (:base64 message-content)}}] |
173 | 190 | "server_tool_use" [{:role :assistant |
174 | 191 | :content {:type :toolCallPrepare |
175 | 192 | :origin :server |
|
734 | 751 | :text (do (swap! received-msgs* str (:text msg)) |
735 | 752 | (lifecycle/send-content! chat-ctx :assistant {:type :text :text (:text msg)})) |
736 | 753 | :url (lifecycle/send-content! chat-ctx :assistant {:type :url :title (:title msg) :url (:url msg)}) |
| 754 | + :image (let [client-content {:type :image |
| 755 | + :media-type (:media-type msg) |
| 756 | + :base64 (:base64 msg)} |
| 757 | + history-content (assoc-some |
| 758 | + {:media-type (:media-type msg) |
| 759 | + :base64 (:base64 msg)} |
| 760 | + :id (:id msg))] |
| 761 | + ;; Provider normalize-messages converts this role back to a user-role image for replay. |
| 762 | + (add-to-history! {:role "image_generation_call" |
| 763 | + :content history-content}) |
| 764 | + (lifecycle/send-content! chat-ctx :assistant client-content)) |
737 | 765 | :limit-reached (do (lifecycle/send-content! |
738 | 766 | chat-ctx |
739 | 767 | :system |
|
897 | 925 | (tc/transition-tool-call! db* chat-ctx id :cleanup-finished |
898 | 926 | {:name (get-in (tc/get-tool-call-state @db* chat-id id) [:name] "web_search")})) |
899 | 927 | nil))) |
| 928 | + :on-server-image-generation (fn [{:keys [status id name]}] |
| 929 | + (lifecycle/assert-chat-not-stopped! chat-ctx) |
| 930 | + (let [summary "Generating image"] |
| 931 | + (case status |
| 932 | + :started (do |
| 933 | + (swap! server-tool-times* assoc id (System/currentTimeMillis)) |
| 934 | + (tc/transition-tool-call! db* chat-ctx id :tool-prepare |
| 935 | + {:name name |
| 936 | + :server :llm |
| 937 | + :origin :server |
| 938 | + :arguments-text "" |
| 939 | + :summary summary}) |
| 940 | + (tc/transition-tool-call! db* chat-ctx id :tool-run |
| 941 | + {:approved?* (promise) |
| 942 | + :future-cleanup-complete?* (promise) |
| 943 | + :name name |
| 944 | + :server :llm |
| 945 | + :origin :server |
| 946 | + :arguments {} |
| 947 | + :manual-approval false |
| 948 | + :summary summary}) |
| 949 | + (tc/transition-tool-call! db* chat-ctx id :approval-allow |
| 950 | + {:reason :server-tool}) |
| 951 | + (tc/transition-tool-call! db* chat-ctx id :execution-start |
| 952 | + {:delayed-future (delay nil) |
| 953 | + :origin :server |
| 954 | + :name name |
| 955 | + :server :llm |
| 956 | + :arguments {} |
| 957 | + :start-time (System/currentTimeMillis) |
| 958 | + :summary summary |
| 959 | + :progress-text "Generating image"})) |
| 960 | + :finished (let [start-time (get @server-tool-times* id) |
| 961 | + total-time-ms (if start-time |
| 962 | + (- (System/currentTimeMillis) start-time) |
| 963 | + 0) |
| 964 | + resolved-name (get-in (tc/get-tool-call-state @db* chat-id id) [:name] "image_generation")] |
| 965 | + (tc/transition-tool-call! db* chat-ctx id :execution-end |
| 966 | + {:origin :server |
| 967 | + :name resolved-name |
| 968 | + :server :llm |
| 969 | + :arguments {} |
| 970 | + :error false |
| 971 | + :outputs [{:type :text :text "Generated image (png)"}] |
| 972 | + :total-time-ms total-time-ms |
| 973 | + :progress-text "Generating" |
| 974 | + :summary summary}) |
| 975 | + (tc/transition-tool-call! db* chat-ctx id :cleanup-finished |
| 976 | + {:name resolved-name})) |
| 977 | + nil))) |
900 | 978 | :on-error (fn [{:keys [message exception] :as error-data}] |
901 | 979 | (let [{error-type :error/type} (llm-providers.errors/classify-error error-data) |
902 | 980 | db @db* |
|
0 commit comments