(messages: ApiMessage[])
| 544 | * @returns The filtered history that should be sent to the API |
| 545 | */ |
| 546 | export function getEffectiveApiHistory(messages: ApiMessage[]): ApiMessage[] { |
| 547 | // Find the most recent summary message |
| 548 | const lastSummary = findLast(messages, (msg) => msg.isSummary === true) |
| 549 | |
| 550 | if (lastSummary) { |
| 551 | // Fresh start model: return only messages from the summary onwards |
| 552 | const summaryIndex = messages.indexOf(lastSummary) |
| 553 | let messagesFromSummary = messages.slice(summaryIndex) |
| 554 | |
| 555 | // Collect all tool_use IDs from assistant messages in the result |
| 556 | // This is needed to filter out orphan tool_result blocks that reference |
| 557 | // tool_use IDs from messages that were condensed away |
| 558 | const toolUseIds = new Set<string>() |
| 559 | for (const msg of messagesFromSummary) { |
| 560 | if (msg.role === "assistant" && Array.isArray(msg.content)) { |
| 561 | for (const block of msg.content) { |
| 562 | if (block.type === "tool_use" && (block as Anthropic.Messages.ToolUseBlockParam).id) { |
| 563 | toolUseIds.add((block as Anthropic.Messages.ToolUseBlockParam).id) |
| 564 | } |
| 565 | } |
| 566 | } |
| 567 | } |
| 568 | |
| 569 | // Filter out orphan tool_result blocks from user messages |
| 570 | messagesFromSummary = messagesFromSummary |
| 571 | .map((msg) => { |
| 572 | if (msg.role === "user" && Array.isArray(msg.content)) { |
| 573 | const filteredContent = msg.content.filter((block) => { |
| 574 | if (block.type === "tool_result") { |
| 575 | return toolUseIds.has((block as Anthropic.Messages.ToolResultBlockParam).tool_use_id) |
| 576 | } |
| 577 | return true |
| 578 | }) |
| 579 | // If all content was filtered out, mark for removal |
| 580 | if (filteredContent.length === 0) { |
| 581 | return null |
| 582 | } |
| 583 | // If some content was filtered, return updated message |
| 584 | if (filteredContent.length !== msg.content.length) { |
| 585 | return { ...msg, content: filteredContent } |
| 586 | } |
| 587 | } |
| 588 | return msg |
| 589 | }) |
| 590 | .filter((msg): msg is ApiMessage => msg !== null) |
| 591 | |
| 592 | // Still need to filter out any truncated messages within this range |
| 593 | const existingTruncationIds = new Set<string>() |
| 594 | for (const msg of messagesFromSummary) { |
| 595 | if (msg.isTruncationMarker && msg.truncationId) { |
| 596 | existingTruncationIds.add(msg.truncationId) |
| 597 | } |
| 598 | } |
| 599 | |
| 600 | return messagesFromSummary.filter((msg) => { |
| 601 | // Filter out truncated messages if their truncation marker exists |
| 602 | if (msg.truncationParent && existingTruncationIds.has(msg.truncationParent)) { |
| 603 | return false |
no test coverage detected