| 65 | // the last content part regardless of type — that's the breakpoint position |
| 66 | // in tool-result-only messages too. |
| 67 | const markMessageAt = (messages: ReadonlyArray<Message>, index: number, hint: CacheHint): ReadonlyArray<Message> => { |
| 68 | if (index < 0 || index >= messages.length) return messages |
| 69 | const target = messages[index]! |
| 70 | if (target.content.length === 0) return messages |
| 71 | const lastTextIndex = target.content.findLastIndex((part) => part.type === "text") |
| 72 | const markAt = lastTextIndex >= 0 ? lastTextIndex : target.content.length - 1 |
| 73 | const existing = target.content[markAt]! |
| 74 | if ("cache" in existing && existing.cache) return messages |
| 75 | const nextContent = target.content.map((part, i) => (i === markAt ? ({ ...part, cache: hint } as ContentPart) : part)) |
| 76 | const next = new Message({ ...target, content: nextContent }) |
| 77 | // Single pass over `messages`, substituting the one updated entry. Long |
| 78 | // conversations call this on every request, so avoid `.map()` here — its |
| 79 | // closure dispatch and identity copies show up in profiling. |
| 80 | const result = messages.slice() |
| 81 | result[index] = next |
| 82 | return result |
| 83 | } |
| 84 | |
| 85 | const markMessages = ( |
| 86 | messages: ReadonlyArray<Message>, |