okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
deef7f4
main
1//// Legacy cursor migration, chunked summaries, and image accounting are costly 2//// or impossible to drive reliably through the shared E2E daemon. 3 4import albedo/daemon/store 5import albedo/harness/compaction 6import albedo/harness/extensions 7import albedo/harness/extensions/rolling/extension as rolling 8import albedo/harness/runtime 9import albedo/openai_api/types 10import gleam/dynamic/decode 11import gleam/erlang/process 12import gleam/int 13import gleam/list 14import gleam/option .{ None , Some } 15import gleam/result 16import gleam/string 17import gleeunit/should 18import harness/session_fixture 19import sqlight 20 21fn host ( config : rolling . Config ) -> #( runtime . Runtime , runtime . Session ) { 22let installed = [ rolling . configured_extension ( config )] 23let assert Ok ( host ) = 24runtime . start_with_config ( 25":memory:" , 26extensions . Config ( installed , [ "rolling" ]), 27) 28session_fixture . initialise ( host ) 29session_fixture . create ( host , "rolling-test" , "/tmp" ) 30let assert Ok ( session ) = runtime . open_session ( host , "rolling-test" , "/tmp" ) 31#( host , session ) 32} 33 34fn long_history () -> List ( types . Input ) { 35[ 36types . User ( "u1 " <> string . repeat ( "a" , 380 )), 37types . Assistant ( "a1 " <> string . repeat ( "b" , 380 )), 38types . User ( "u2 " <> string . repeat ( "c" , 380 )), 39types . Assistant ( "a2 " <> string . repeat ( "d" , 380 )), 40types . User ( "u3 " <> string . repeat ( "e" , 380 )), 41types . Assistant ( "a3 " <> string . repeat ( "f" , 380 )), 42] 43} 44 45/// A row saved before cuts counted user messages validates the old way, by 46/// item count under its source. A projection only reads it; the next 47/// compaction saves the portable form in its place. 48pub fn legacy_item_count_state_resumes_and_compaction_upgrades_it_test () -> Nil { 49let #( host , session ) = host ( rolling . Config ( Some ( 10_000 ), 90 , 25 )) 50let history = long_history () 51let ledger = runtime . ledger ( host ) 52let assert Ok ( _ ) = 53store . query ( ledger , fn ( db ) { 54sqlight . query ( 55"INSERT INTO rolling_compaction_state(session,summary,cutoff_count,source_hash) VALUES(?,?,?,?)" , 56db , 57[ 58sqlight . text ( "rolling-test" ), 59sqlight . text ( "legacy facts" ), 60sqlight . int ( 4 ), 61sqlight . text ( 62compaction . fingerprint (#( "model-a" , list . take ( history , 4 ))), 63), 64], 65decode . dynamic , 66) 67}) 68let assert Ok ([ types . User ( summary ), _ , .. tail ]) = 69runtime . prepare_history_with ( 70host , 71session , 72"model-a" , 73"" , 74fn ( _ ) { Error ( "must not summarize" ) }, 75history , 76) 77string . contains ( summary , "legacy facts" ) |> should . be_true 78tail |> should . equal ( list . drop ( history , 4 )) 79let assert Ok ([#( 4 , legacy )]) = saved_cut ( ledger ) 80string . starts_with ( legacy , "users:" ) |> should . be_false 81let longer = 82list . append ( history , [ 83types . User ( "u4 " <> string . repeat ( "g" , 380 )), 84types . Assistant ( "a4 " <> string . repeat ( "h" , 380 )), 85]) 86let assert Ok ( _ ) = 87runtime . compact_history_scoped ( 88host , 89session , 90"model-a" , 91"model-a" , 92None , 93"" , 94fn ( request : compaction . SummaryRequest ) { 95request . previous |> should . equal ( Some ( "legacy facts" )) 96Ok ( "folded facts" ) 97}, 98longer , 99) 100let assert Ok ([#( 3 , cut_hash )]) = saved_cut ( ledger ) 101string . starts_with ( cut_hash , "users:" ) |> should . be_true 102runtime . stop ( host ) 103} 104 105fn saved_cut ( ledger : store . Store ) -> Result ( List (#( Int , String )), String ) { 106store . query ( ledger , fn ( db ) { 107sqlight . query ( 108"SELECT cutoff_count,source_hash FROM rolling_compaction_state" , 109db , 110[], 111{ 112use users <- decode . field ( 0 , decode . int ) 113use hash <- decode . field ( 1 , decode . string ) 114decode . success (#( users , hash )) 115}, 116) 117|> result . map_error ( string . inspect ) 118}) 119} 120 121/// A long eviction folds into the summary chunk by chunk, each request under 122/// half the window, carrying the summary forward. 123pub fn long_eviction_summarizes_in_chunks_test () -> Nil { 124let #( host , session ) = host ( rolling . Config ( Some ( 2000 ), 90 , 25 )) 125let history = 126list . flatten ( list . repeat ( long_history (), 4 )) 127|> list . index_map ( fn ( input , index ) { 128case input { 129types . User ( text ) -> types . User ( int . to_string ( index ) <> text ) 130other -> other 131} 132}) 133let requests = process . new_subject () 134let assert Ok ( _ ) = 135runtime . compact_history_scoped ( 136host , 137session , 138"model-a" , 139"model-a" , 140None , 141"" , 142fn ( request : compaction . SummaryRequest ) { 143process . send ( requests , request ) 144Ok ( "after " <> int . to_string ( list . length ( request . evicted ))) 145}, 146history , 147) 148let assert Ok ( compaction . SummaryRequest ( _ , None , first , _ , _ )) = 149process . receive ( requests , 0 ) 150let assert Ok ( compaction . SummaryRequest ( _ , Some ( previous ), _ , _ , _ )) = 151process . receive ( requests , 0 ) 152previous |> should . equal ( "after " <> int . to_string ( list . length ( first ))) 153should . be_true ( compaction . estimate_inputs ( first ) <= 1000 ) 154runtime . stop ( host ) 155} 156 157pub fn image_estimate_uses_dimensions_not_base64_transport_size_test () -> Nil { 158let payload = string . repeat ( "A" , 5 * 1024 * 1024 ) 159let assert Ok ( image ) = 160types . image ( "image/png" , payload , 1024 , 1024 , 3 * 1024 * 1024 ) 161let input = types . UserImage ( "inspect" , [ image ]) 162let transport_includes_payload = 163compaction . input_bytes ( input ) > 5 * 1024 * 1024 164transport_includes_payload |> should . equal ( True ) 165let estimate_excludes_payload = compaction . estimate_input ( input ) < 1000 166estimate_excludes_payload |> should . equal ( True ) 167}