| 128 | 147 | message.content = `${charName}: ${message.content}`; |
| 129 | 148 | } |
| 130 | 149 | message.role = 'user'; |
| 150 | + |
| 151 | + // Delete name here so it doesn't get added later |
| 152 | + delete message.name; |
| 131 | 153 | } |
| 132 | | - }); |
| 133 | 154 | |
| 134 | | - // Shouldn't be conditional anymore, messages api expects the last role to be user unless we're explicitly prefilling |
| 155 | + // Convert everything to an array of it would be easier to work with |
| 135 | | - if (prefillString) { |
| 156 | + if (typeof message.content === 'string') { |
| 136 | | - messages.push({ |
| 157 | + // Take care of name properties since claude messages don't support them |
| 137 | | - role: 'assistant', |
| 158 | + if (message.name) { |
| 138 | | - content: prefillString.trimEnd(), |
| 159 | + message.content = `${message.name}: ${message.content}`; |
| 139 | | - }); |
| 140 | 160 | } |
| 141 | 161 | |
| 142 | | - // Since the messaging endpoint only supports user assistant roles in turns, we have to merge messages with the same role if they follow eachother |
| 162 | + message.content = [{ type: 'text', text: message.content }]; |
| 143 | | - // Also handle multi-modality, holy slop. |
| 163 | + } else if (Array.isArray(message.content)) { |
| 144 | | - let mergedMessages = []; |
| 164 | + message.content = message.content.map((content) => { |
| 145 | | - messages.forEach((message) => { |
| 165 | + if (content.type === 'image_url') { |
| 146 | 166 | const imageEntry = message.content?.[1]?.image_url; |
| 147 | 167 | const imageData = imageEntry?.url; |
| 148 | 168 | const mimeType = imageData?.split(';')?.[0].split(':')?.[1]; |
| 149 | 169 | const base64Data = imageData?.split(',')?.[1]; |
| 150 | 170 | |
| 151 | | - // Take care of name properties since claude messages don't support them |
| 171 | + return { |
| 172 | + type: 'image', |
| 173 | + source: { |
| 174 | + type: 'base64', |
| 175 | + media_type: mimeType, |
| 176 | + data: base64Data, |
| 177 | + }, |
| 178 | + }; |
| 179 | + } |
| 180 | + |
| 181 | + if (content.type === 'text') { |
| 152 | 182 | if (message.name) { |
| 153 | | - if (Array.isArray(message.content)) { |
| 183 | + content.text = `${message.name}: ${content.text}`; |
| 154 | | - message.content[0].text = `${message.name}: ${message.content[0].text}`; |
| 155 | | - } else { |
| 156 | | - message.content = `${message.name}: ${message.content}`; |
| 157 | 184 | } |
| 185 | + |
| 186 | + return content; |
| 187 | + } |
| 188 | + |
| 189 | + return content; |
| 190 | + }); |
| 191 | + } |
| 192 | + |
| 193 | + // Remove offending properties |
| 158 | 194 | delete message.name; |
| 195 | + delete message.tool_calls; |
| 196 | + delete message.tool_call_id; |
| 197 | + }); |
| 198 | + |
| 199 | + // Images in assistant messages should be moved to the next user message |
| 200 | + for (let i = 0; i < messages.length; i++) { |
| 201 | + if (messages[i].role === 'assistant' && messages[i].content.some(c => c.type === 'image')) { |
| 202 | + // Find the next user message |
| 203 | + let j = i + 1; |
| 204 | + while (j < messages.length && messages[j].role !== 'user') { |
| 205 | + j++; |
| 159 | 206 | } |
| 160 | 207 | |
| 161 | | - if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role) { |
| 208 | + // Move the images |
| 162 | 209 | if (Array.isArray(messagej >= messages.content)length) { |
| 163 | | - if (Array.isArray(mergedMessages[mergedMessages.length - 1].content)) { |
| 210 | + // If there is no user message after the assistant message, add a new one |
| 164 | | - mergedMessages[mergedMessages.length - 1].content[0].text += '\n\n' + message.content[0].text; |
| 211 | + messages.splice(i + 1, 0, { role: 'user', content: [] }); |
| 165 | | - } else { |
| 212 | + } |
| 166 | | - mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content[0].text; |
| 213 | + |
| 214 | + messages[j].content.push(...messages[i].content.filter(c => c.type === 'image')); |
| 215 | + messages[i].content = messages[i].content.filter(c => c.type !== 'image'); |
| 167 | 216 | } |
| 168 | | - } else { |
| 169 | | - if (Array.isArray(mergedMessages[mergedMessages.length - 1].content)) { |
| 170 | | - mergedMessages[mergedMessages.length - 1].content[0].text += '\n\n' + message.content; |
| 171 | | - } else { |
| 172 | | - mergedMessages[mergedMessages.length - 1].content += '\n\n' + message.content; |
| 173 | 217 | } |
| 218 | + |
| 219 | + // Shouldn't be conditional anymore, messages api expects the last role to be user unless we're explicitly prefilling |
| 220 | + if (prefillString) { |
| 221 | + messages.push({ |
| 222 | + role: 'assistant', |
| 223 | + // Dangling whitespace are not allowed for prefilling |
| 224 | + content: prefillString.trimEnd(), |
| 225 | + }); |
| 174 | 226 | } |
| 227 | + |
| 228 | + // Since the messaging endpoint only supports user assistant roles in turns, we have to merge messages with the same role if they follow eachother |
| 229 | + // Also handle multi-modality, holy slop. |
| 230 | + let mergedMessages = []; |
| 231 | + messages.forEach((message) => { |
| 232 | + if (mergedMessages.length > 0 && mergedMessages[mergedMessages.length - 1].role === message.role) { |
| 233 | + mergedMessages[mergedMessages.length - 1].content.push(...message.content); |
| 175 | 234 | } else { |
| 176 | 235 | mergedMessages.push(message); |
| 177 | 236 | } |
| 178 | | - if (imageData) { |
| 179 | | - mergedMessages[mergedMessages.length - 1].content = [ |
| 180 | | - { type: 'text', text: mergedMessages[mergedMessages.length - 1].content[0]?.text || mergedMessages[mergedMessages.length - 1].content }, |
| 181 | | - { |
| 182 | | - type: 'image', source: { |
| 183 | | - type: 'base64', |
| 184 | | - media_type: mimeType, |
| 185 | | - data: base64Data, |
| 186 | | - }, |
| 187 | | - }, |
| 188 | | - ]; |
| 189 | | - } |
| 190 | 237 | }); |
| 191 | 238 | |
| 192 | 239 | return { messages: mergedMessages, systemPrompt: systemPrompt.trim() }; |