| 1 | package main |
| 2 | |
| 3 | import "sort" |
| 4 | |
| 5 | func historySliceCandidateRange(src *historySliceSource, req HistorySliceRequest, cursor historySliceCursor, hi int, forward bool) (int, int, error) { |
| 6 | // Turn budget: the oldest visible turn this page may reach. |
| 7 | newestTurn := src.turnAt(hi - 1) |
| 8 | oldestTurn := 0 |
| 9 | if newestTurn > 0 { |
| 10 | oldestTurn = max(newestTurn-req.Turns+1, 1) |
| 11 | } |
| 12 | // turns is non-decreasing: binary-search the first message in the page. |
| 13 | candidateLo := sort.Search(hi, func(i int) bool { return src.turnAt(i) >= oldestTurn }) |
| 14 | if oldestTurn <= 1 { |
| 15 | // A page reaching the first turn also includes the pre-turn messages |
| 16 | // (system prompt), mirroring providerMessagesForVisibleTurnRange. |
| 17 | candidateLo = 0 |
| 18 | } |
| 19 | if src.maxFetch > 0 { |
| 20 | candidateLo = max(candidateLo, hi-min(req.Entries, src.maxFetch)) |
| 21 | } |
| 22 | if forward { |
| 23 | candidateLo = cursor.Before |
| 24 | } |
| 25 | if src.readErr != nil { |
| 26 | return 0, 0, src.readErr |
| 27 | } |
| 28 | // Cold-path raw-span cap: shrink the window forward while the byte span |
| 29 | // is excessive (image-dense windows). |
| 30 | if src.windowBytes != nil { |
| 31 | for candidateLo < hi-1 && src.windowBytes(candidateLo, hi) > historySliceColdWindowBytes { |
| 32 | if forward { |
| 33 | hi-- |
| 34 | } else { |
| 35 | candidateLo++ |
| 36 | } |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | return candidateLo, hi, nil |
| 41 | } |
| 42 | |
| 43 | func completeHistorySlicePage(page HistorySlice, src *historySliceSource, pageStart, hi int, sourceID string) HistorySlice { |
| 44 | for _, e := range page.Entries { |
| 45 | if e.Turn <= 0 { |
| 46 | continue |
| 47 | } |
| 48 | if page.StartTurn == 0 || e.Turn < page.StartTurn { |
| 49 | page.StartTurn = e.Turn |
| 50 | } |
| 51 | if e.Turn > page.EndTurn { |
| 52 | page.EndTurn = e.Turn |
| 53 | } |
| 54 | } |
| 55 | page.HasOlder = pageStart > 0 |
| 56 | if page.HasOlder { |
| 57 | page.NextCursor = encodeHistorySliceCursor(historySliceCursor{ |
| 58 | V: 1, |
| 59 | Revision: src.revision, |
| 60 | RevKnown: src.revKnown, |
| 61 | Digest: src.digest, |
| 62 | Before: pageStart, |
| 63 | Source: sourceID, |
| 64 | }) |
| 65 | } |
| 66 | page.HasNewer = hi < src.total |
| 67 | if page.HasNewer { |
| 68 | page.NewerCursor = encodeHistorySliceCursor(historySliceCursor{V: 1, Revision: src.revision, RevKnown: src.revKnown, Digest: src.digest, Before: hi, Source: sourceID}) |
| 69 | } |
| 70 | return page |
| 71 | } |
| 72 |