(targetApproxTokens: number)
| 174 | } |
| 175 | |
| 176 | function buildMessageHistory(targetApproxTokens: number): Message[] { |
| 177 | const messages: Message[] = [] |
| 178 | const roundsNeeded = Math.max(1, Math.ceil(targetApproxTokens / TOKENS_PER_ROUND)) |
| 179 | const now = Date.now() |
| 180 | |
| 181 | console.log( |
| 182 | ` Building ${roundsNeeded} rounds for ~${targetApproxTokens} tokens ` + |
| 183 | `(est ${TOKENS_PER_ROUND} tokens/round)`, |
| 184 | ) |
| 185 | |
| 186 | for (let i = 0; i < roundsNeeded; i++) { |
| 187 | // Add sentAt timestamps so context-pruner's cache-miss detection works correctly. |
| 188 | // Space messages 30s apart so no cache-miss (>5min gap) is triggered inadvertently. |
| 189 | const sentAt = now - (roundsNeeded - i) * 30_000 |
| 190 | |
| 191 | // User message with diverse word content (~4 chars/token) |
| 192 | const userMsg = createMessage( |
| 193 | 'user', |
| 194 | makeLargeContent(`Round ${i + 1}: `, LARGE_CONTENT_SIZE), |
| 195 | ) |
| 196 | userMsg.sentAt = sentAt |
| 197 | messages.push(userMsg) |
| 198 | |
| 199 | // Assistant response with diverse word content |
| 200 | const assistantMsg = createMessage( |
| 201 | 'assistant', |
| 202 | makeLargeContent(`Response ${i + 1}: `, LARGE_CONTENT_SIZE), |
| 203 | ) |
| 204 | assistantMsg.sentAt = sentAt + 10_000 |
| 205 | messages.push(assistantMsg) |
| 206 | |
| 207 | // Add a tool call pair every other round for realism |
| 208 | if (i % 2 === 0) { |
| 209 | const callId = `call-${i}` |
| 210 | messages.push( |
| 211 | createToolCallMessage(callId, 'read_files', { paths: [`file-${i}.ts`] }), |
| 212 | ) |
| 213 | messages.push( |
| 214 | createToolResultMessage(callId, 'read_files', { |
| 215 | content: makeLargeContent('', LARGE_CONTENT_SIZE / 2), |
| 216 | }), |
| 217 | ) |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | return messages |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Detects whether context pruning occurred by checking for: |
no test coverage detected