okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petfix(compaction): keep stale capability notes and reasoning blobs out of evicted history0a29dfa

main
5.4 KiB147 linesraw
1//// Pure archive bounds and portable compaction cursors guard edge cases
2//// that require unrealistic provider history sizes or projections in E2E.
3//// Frames fitting a provider's edge needs a provider that declares one, and
4//// the E2E fake provider declares none.
5
6import albedo/harness/compaction
7import albedo/harness/extensions/snapcompact/extension as snapcompact
8import albedo/openai_api/types
9import gleam/json
10import gleam/list
11import gleam/option.{None}
12import gleam/string
13import gleeunit/should
14
15pub fn bound_keeps_the_first_and_newest_frames_test() -> Nil {
16  let shape = snapcompact.Shape(11, 16, 256, 1)
17  // 23 cells per frame: ten frames, bounded to four.
18  let pages =
19    list.map(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"], string.repeat(
20      _,
21      23,
22    ))
23  let #(kept, dropped) = snapcompact.bound(shape, 4, string.concat(pages))
24  snapcompact.paginate(shape, kept)
25  |> should.equal([
26    string.repeat("a", 23),
27    string.repeat("h", 23),
28    string.repeat("i", 23),
29    string.repeat("j", 23),
30  ])
31  dropped |> should.equal(6 * 23)
32  snapcompact.bound(shape, 10, string.concat(pages))
33  |> should.equal(#(string.concat(pages), 0))
34}
35
36pub fn a_full_frame_renders_inside_the_provider_edge_test() -> Nil {
37  let limits = types.ImageLimits(max_edge: 1000, max_images: None)
38  use model <- list.each(["gemini-3-pro", "gpt-5", "claude-opus-5-5"])
39  let shape = snapcompact.fit(snapcompact.shape(model), limits)
40  let assert [page, ..] =
41    snapcompact.paginate(shape, string.repeat("x\u{2588}", 100_000))
42  let assert Ok(#(width, height, _)) =
43    snapcompact.render_frame_test(page, shape.advance, shape.pitch, shape.width)
44  { width <= 1000 && height <= 1000 } |> should.be_true
45}
46
47pub fn paginate_never_splits_a_multibyte_cell_test() -> Nil {
48  let shape = snapcompact.Shape(11, 16, 256, 1)
49  // The 23rd cell, which fills the first frame, is the 3-byte newline cell.
50  let first = string.repeat("a", 22) <> "\u{2588}"
51  let second = string.repeat("b", 23)
52  snapcompact.paginate(shape, first <> second)
53  |> should.equal([first, second])
54}
55
56pub fn split_tail_keeps_whole_units_within_budget_test() -> Nil {
57  let history = [
58    types.User("first"),
59    types.Assistant("a1"),
60    types.ToolOutput("t1", "o1", []),
61    types.User("second"),
62    types.Assistant("a2"),
63    types.User("third"),
64  ]
65  // A one-token budget still keeps the newest unit.
66  compaction.split_tail(history, 1)
67  |> should.equal(#(list.take(history, 5), [types.User("third")]))
68  // A single unit is never split.
69  compaction.split_tail([types.User("only"), types.Assistant("a")], 1)
70  |> should.equal(#([], [types.User("only"), types.Assistant("a")]))
71}
72
73pub fn split_tail_never_orphans_a_tool_output_test() -> Nil {
74  let history = [
75    types.User("first"),
76    types.User("second"),
77    types.Assistant("calling"),
78    types.ToolOutput("t9", "result", []),
79    types.User("third"),
80  ]
81  let #(evicted, tail) = compaction.split_tail(history, 1)
82  evicted |> list.last |> should.equal(Ok(types.ToolOutput("t9", "result", [])))
83  tail |> should.equal([types.User("third")])
84}
85
86pub fn cut_resumes_across_projection_changes_test() -> Nil {
87  let prefix = [types.User("one"), types.Assistant("a"), types.User("two")]
88  let cut = compaction.cut_of(prefix)
89  cut.users |> should.equal(2)
90  // Another provider merges or drops assistant output; users stay put.
91  let other = [types.User("one"), types.User("two"), types.Assistant("b")]
92  let assert Ok(#(evicted, rest)) =
93    compaction.resume(list.append(other, [types.User("three")]), cut)
94  evicted |> should.equal(other)
95  rest |> should.equal([types.User("three")])
96  // A rewritten user message no longer matches.
97  compaction.resume(
98    [types.User("uno"), types.User("two"), types.User("three")],
99    cut,
100  )
101  |> should.equal(Error(Nil))
102  // No user message left after the cut: nothing to resume into.
103  compaction.resume(prefix, cut) |> should.equal(Error(Nil))
104}
105
106fn replay(protocol: types.Protocol, body: String) -> types.Input {
107  let assert Ok(item) = json.parse(body, types.replay_decoder(protocol))
108  types.Replay(item)
109}
110
111pub fn responses_items_render_as_text_and_calls_without_reasoning_test() -> Nil {
112  let archive =
113    snapcompact.normalize([
114      replay(
115        types.Responses,
116        "{\"type\":\"reasoning\",\"id\":\"rs_1\",\"encrypted_content\":\"OPAQUEBLOB\"}",
117      ),
118      replay(
119        types.Responses,
120        "{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"checking\"}]}",
121      ),
122      replay(
123        types.Responses,
124        "{\"type\":\"function_call\",\"call_id\":\"c1\",\"name\":\"python\",\"arguments\":\"{\\\"code\\\":\\\"print(1)\\\"}\"}",
125      ),
126    ])
127  string.contains(archive, "OPAQUEBLOB") |> should.be_false
128  string.contains(archive, "¶turn:") |> should.be_false
129  string.contains(archive, "¶ai: checking") |> should.be_true
130  string.contains(archive, "→ python(code = print(1))") |> should.be_true
131}
132
133pub fn evicted_capability_notes_do_not_reach_the_archive_test() -> Nil {
134  let archive =
135    snapcompact.normalize([
136      types.User(
137        "<system-note origin=\"capabilities changed\">old catalog</system-note>",
138      ),
139      types.User(
140        "<system-note origin=\"python kernel\">kept note</system-note>",
141      ),
142      types.User("real question"),
143    ])
144  string.contains(archive, "old catalog") |> should.be_false
145  string.contains(archive, "kept note") |> should.be_true
146  string.contains(archive, "real question") |> should.be_true
147}