(messages: ApiMessage[], fracToRemove: number, taskId: string)
| 91 | * @returns {TruncationResult} Object containing the tagged messages, truncation ID, and count of messages removed. |
| 92 | */ |
| 93 | export function truncateConversation(messages: ApiMessage[], fracToRemove: number, taskId: string): TruncationResult { |
| 94 | TelemetryService.instance.captureSlidingWindowTruncation(taskId) |
| 95 | |
| 96 | const truncationId = crypto.randomUUID() |
| 97 | |
| 98 | // Filter to only visible messages (those not already truncated) |
| 99 | // We need to track original indices to correctly tag messages in the full array |
| 100 | const visibleIndices: number[] = [] |
| 101 | messages.forEach((msg, index) => { |
| 102 | if (!msg.truncationParent && !msg.isTruncationMarker) { |
| 103 | visibleIndices.push(index) |
| 104 | } |
| 105 | }) |
| 106 | |
| 107 | // Calculate how many visible messages to truncate (excluding first visible message) |
| 108 | const visibleCount = visibleIndices.length |
| 109 | const rawMessagesToRemove = Math.floor((visibleCount - 1) * fracToRemove) |
| 110 | const messagesToRemove = rawMessagesToRemove - (rawMessagesToRemove % 2) |
| 111 | |
| 112 | if (messagesToRemove <= 0) { |
| 113 | // Nothing to truncate |
| 114 | return { |
| 115 | messages, |
| 116 | truncationId, |
| 117 | messagesRemoved: 0, |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | // Get the indices of visible messages to truncate (skip first visible, take next N) |
| 122 | const indicesToTruncate = new Set(visibleIndices.slice(1, messagesToRemove + 1)) |
| 123 | |
| 124 | // Tag messages that are being "truncated" (hidden from API calls) |
| 125 | const taggedMessages = messages.map((msg, index) => { |
| 126 | if (indicesToTruncate.has(index)) { |
| 127 | return { ...msg, truncationParent: truncationId } |
| 128 | } |
| 129 | return msg |
| 130 | }) |
| 131 | |
| 132 | // Find the actual boundary - the index right after the last truncated message |
| 133 | const lastTruncatedVisibleIndex = visibleIndices[messagesToRemove] // Last visible message being truncated |
| 134 | // If all visible messages except the first are truncated, insert marker at the end |
| 135 | const firstKeptVisibleIndex = visibleIndices[messagesToRemove + 1] ?? taggedMessages.length |
| 136 | |
| 137 | // Insert truncation marker at the actual boundary (between last truncated and first kept) |
| 138 | const firstKeptTs = messages[firstKeptVisibleIndex]?.ts ?? Date.now() |
| 139 | const truncationMarker: ApiMessage = { |
| 140 | role: "user", |
| 141 | content: `[Sliding window truncation: ${messagesToRemove} messages hidden to reduce context]`, |
| 142 | ts: firstKeptTs - 1, |
| 143 | isTruncationMarker: true, |
| 144 | truncationId, |
| 145 | } |
| 146 | |
| 147 | // Insert marker at the boundary position |
| 148 | // Find where to insert: right before the first kept visible message |
| 149 | const insertPosition = firstKeptVisibleIndex |
| 150 | const result = [ |
no test coverage detected