(requests: MinimalRequest[])
| 179 | * `requests` should already be filtered to the main-agent requests in order. |
| 180 | */ |
| 181 | export function analyzePasses(requests: MinimalRequest[]): PassComparison[] { |
| 182 | const out: PassComparison[] = []; |
| 183 | const snaps = requests.map((r) => buildSegments(r.body)); |
| 184 | for (let k = 0; k < snaps.length; k += 1) { |
| 185 | if (k === 0) { |
| 186 | out.push({ |
| 187 | index: 0, |
| 188 | verdict: "BASE", |
| 189 | divergeIndex: -1, |
| 190 | divergeSegmentId: null, |
| 191 | cachedPrefixBytes: 0, |
| 192 | cachedPrefixAt: "(base)", |
| 193 | diff: null, |
| 194 | }); |
| 195 | continue; |
| 196 | } |
| 197 | const prev = snaps[k - 1]; |
| 198 | const cur = snaps[k]; |
| 199 | const idx = firstDivergence(prev, cur); |
| 200 | if (idx === -1) { |
| 201 | out.push({ |
| 202 | index: k, |
| 203 | verdict: "SAME", |
| 204 | divergeIndex: -1, |
| 205 | divergeSegmentId: null, |
| 206 | cachedPrefixBytes: cachedPrefixBytes(cur, -1).bytes, |
| 207 | cachedPrefixAt: cachedPrefixBytes(cur, -1).at, |
| 208 | diff: null, |
| 209 | }); |
| 210 | continue; |
| 211 | } |
| 212 | // The cache was WRITTEN during the previous request, at the previous |
| 213 | // request's cache_control breakpoints. So the reusable cached prefix for |
| 214 | // this transition is bounded by PREV's last breakpoint — not cur's. |
| 215 | // OpenCode walks the breakpoint forward to the last 1-2 messages every |
| 216 | // turn, so cur's last breakpoint sits AHEAD of freshly-appended tail |
| 217 | // content; comparing against it would mis-flag benign tail growth as a |
| 218 | // bust. The correct invariant: everything up to and including prev's |
| 219 | // last breakpoint must stay byte-identical in cur. |
| 220 | // STABLE: first divergence is strictly AFTER prev's last breakpoint |
| 221 | // (only uncached tail changed, or pure append). |
| 222 | // BUST: first divergence is at/before prev's last breakpoint |
| 223 | // (content that was cached got rewritten). |
| 224 | // prevLastBreakpoint === -1 means prev cached nothing → no bust possible. |
| 225 | const prevLastBreakpoint = lastBreakpointIndex(prev); |
| 226 | const verdict: BustVerdict = idx > prevLastBreakpoint ? "STABLE" : "BUST"; |
| 227 | const cp = cachedPrefixBytes(cur, idx); |
| 228 | const seg = cur[idx] ?? prev[idx]; |
| 229 | out.push({ |
| 230 | index: k, |
| 231 | verdict, |
| 232 | divergeIndex: idx, |
| 233 | divergeSegmentId: seg?.id ?? `seg[${idx}]`, |
| 234 | cachedPrefixBytes: cp.bytes, |
| 235 | cachedPrefixAt: cp.at, |
| 236 | diff: |
| 237 | verdict === "BUST" |
| 238 | ? { |
no test coverage detected