* Extracts and validates the tool call sequence from the end of a message array. * Tool sequences consist of: [assistant_with_tool_calls, tool_response_1, tool_response_2, ...] * or just a single user message. * * @param messages - Array of chat messages (will be modified by popping messages) *
(messages: ChatMessage[])
| 226 | * @returns Array of messages that form the tool sequence |
| 227 | */ |
| 228 | function extractToolSequence(messages: ChatMessage[]): ChatMessage[] { |
| 229 | const lastMsg = messages.pop(); |
| 230 | if (!lastMsg) { |
| 231 | throw new Error("Error parsing chat history: no user/tool message found"); |
| 232 | } |
| 233 | |
| 234 | const toolSequence: ChatMessage[] = []; |
| 235 | |
| 236 | if (lastMsg.role === "tool") { |
| 237 | toolSequence.push(lastMsg); |
| 238 | |
| 239 | // Collect all consecutive tool messages from the end |
| 240 | while ( |
| 241 | messages.length > 0 && |
| 242 | messages[messages.length - 1].role === "tool" |
| 243 | ) { |
| 244 | toolSequence.unshift(messages.pop()!); |
| 245 | } |
| 246 | |
| 247 | // Get the assistant message with tool calls |
| 248 | const assistantMsg = messages.pop(); |
| 249 | if (assistantMsg) { |
| 250 | toolSequence.unshift(assistantMsg); |
| 251 | |
| 252 | // Validate that all tool messages have matching tool call IDs |
| 253 | for (const toolMsg of toolSequence.slice(1)) { |
| 254 | // Skip assistant message |
| 255 | if ( |
| 256 | toolMsg.role === "tool" && |
| 257 | !messageHasToolCallId(assistantMsg, toolMsg.toolCallId) |
| 258 | ) { |
| 259 | throw new Error( |
| 260 | `Error parsing chat history: no tool call found to match tool output for id "${toolMsg.toolCallId}"`, |
| 261 | ); |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | } else if (lastMsg.role === "assistant" || lastMsg.role === "thinking") { |
| 266 | toolSequence.push(lastMsg); |
| 267 | while ( |
| 268 | messages.length > 0 && |
| 269 | (messages[messages.length - 1].role === "thinking" || |
| 270 | messages[messages.length - 1].role === "assistant") |
| 271 | ) { |
| 272 | toolSequence.unshift(messages.pop()!); |
| 273 | } |
| 274 | } else { |
| 275 | // Single user message |
| 276 | toolSequence.push(lastMsg); |
| 277 | } |
| 278 | |
| 279 | return toolSequence; |
| 280 | } |
| 281 | |
| 282 | function pruneLinesFromTop( |
| 283 | prompt: string, |
no test coverage detected