okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
a88d329
main
1"""Provider changes preserve history and confirmed configuration across restart.""" 2 3import json 4import time 5import urllib .error 6import urllib .parse 7 8from harness import exclusive 9 10from integration_support import IntegrationScenario ,contents 11 12 13class ProviderBindingTests (IntegrationScenario ): 14# exclusive: changes global provider settings and restarts the daemon 15@exclusive 16def test_provider_switch_projects_history_across_protocols_and_restart (self ): 17for protocol in ("responses" ,"chat_completions" ): 18with self .subTest (protocol = protocol ): 19app = self .app_for (protocol ) 20self .settings (app ,protocol ) 21session = self .default_session (app ) 22self .change ( 23app , 24session , 25 {"provider_profile" :"alpha" ,"model" :"initial-alpha" }, 26 ) 27self .send (app ,session ,"build switch history" ) 28other = ( 29"responses" 30if protocol == "chat_completions" 31else "chat_completions" 32 ) 33for fields in ( 34 {"provider_profile" :"gamma" ,"model" :"chosen-gamma" }, 35 {"model" :"renamed-gamma" }, 36 ): 37self .change (app ,session ,fields ) 38selected = self .read (app ,f"/sessions/ { session } " ) 39self .assertEqual ( 40 (selected ["provider_profile" ],selected ["model" ]), 41 ("gamma" ,"renamed-gamma" ), 42 ) 43with self .assertRaises (urllib .error .HTTPError )as caught : 44self .change ( 45app ,session , {"provider_profile" :"unknown" ,"model" :"wrong" } 46 ) 47self .assertIn (caught .exception .code , (400 ,409 )) 48start = len (self .provider .requests ) 49self .send (app ,session ,"after first switch" ) 50gamma = self .records (start ) 51self .assertTrue (gamma ) 52self .assertTrue ( 53all ( 54"/gamma/v1/" in r ["path" ] 55and r ["model" ]== "renamed-gamma" 56and r ["authorization" ]== "Bearer gamma-1" 57for r in gamma 58 ) 59 ) 60self .assert_projected ( 61gamma [0 ],other , ["build switch history" ,"after first switch" ] 62 ) 63# Per-session selection does not silently rewrite global defaults. 64default = self .read (app ,f"/sessions/ { self . default_session ( app ) } " ) 65self .assertEqual ( 66 (default ["provider_profile" ],default ["model" ]), 67 ("alpha" ,"fixture-alpha" ), 68 ) 69self .change ( 70app , 71session , 72 {"provider_profile" :"alpha" ,"model" :"returned-alpha" }, 73 ) 74start = len (self .provider .requests ) 75self .send (app ,session ,"after second switch" ) 76returned = self .records (start ) 77self .assertTrue ( 78all ( 79"/alpha/v1/" in r ["path" ]and r ["model" ]== "returned-alpha" 80for r in returned 81 ) 82 ) 83users = [ 84"build switch history" , 85"after first switch" , 86"after second switch" , 87 ] 88self .assert_projected (returned [0 ],protocol ,users ) 89self .restart (app ) 90start = len (self .provider .requests ) 91self .send (app ,session ,"after persisted restart" ) 92restarted = self .records (start ) 93self .assertTrue ( 94all ( 95"/alpha/v1/" in r ["path" ]and r ["model" ]== "returned-alpha" 96for r in restarted 97 ) 98 ) 99self .assert_projected ( 100restarted [0 ],protocol ,users + ["after persisted restart" ] 101 ) 102 103# exclusive: changes global provider settings and restarts the daemon 104@exclusive 105def test_provider_config_reload_and_interrupt_recovery (self ): 106for protocol in ("responses" ,"chat_completions" ): 107with self .subTest (protocol = protocol ): 108app = self .app_for (protocol ) 109self .settings (app ,protocol ) 110initial = json .loads ((app .home / "config.json" ).read_text ()) 111initial ["providers" ]["alpha" ]["apiKey" ]= "alpha-1" 112 (app .home / "config.json" ).write_text (json .dumps (initial )) 113session = self .default_session (app ) 114self .settings (app ,protocol ,active = "beta" ) 115start = len (self .provider .requests ) 116self .send (app ,session ,"write and inspect a file" ) 117alpha = self .records (start ) 118self .assertTrue (alpha ) 119self .assertTrue ( 120all ( 121"/alpha/v1/" in r ["path" ] 122and r ["authorization" ]== "Bearer alpha-2" 123and r ["model" ]== "fixture-alpha" 124for r in alpha 125 ) 126 ) 127second = self .default_session (app ) 128info = next ( 129s 130for s in self .read (app ,"/sessions?scope=all" )["items" ] 131if s ["id" ]== second 132 ) 133self .assertEqual ( 134 (info ["provider_profile" ],info ["model" ]), ("beta" ,"fixture-beta" ) 135 ) 136start = len (self .provider .requests ) 137app .prompt (second ,"hang" ).close () 138self .wait_for_request (start ) 139for fields in ( 140 {"workspace" :str (app .root )}, 141 {"provider_profile" :"gamma" ,"model" :"busy-rejected" }, 142 ): 143with self .assertRaises (urllib .error .HTTPError )as caught : 144self .change (app ,second ,fields ) 145self .assertEqual (caught .exception .code ,409 ) 146info = next ( 147s 148for s in self .read (app ,"/sessions?scope=all" )["items" ] 149if s ["id" ]== second 150 ) 151self .assertEqual ( 152 (info ["workspace" ],info ["provider_profile" ]), 153 (str (app .workspace ),"beta" ), 154 ) 155captured = self .read (app ,f"/sessions/ { second } ?tail=0" ) 156app .api ( 157f"/sessions/ { second } /interrupt" , 158 { 159"run_id" :captured ["status" ]["run_id" ], 160"through_input_order" :captured ["input_order" ], 161 }, 162 ).close () 163app .idle (second ) 164beta = self .records (start ) 165self .assertTrue (beta ) 166self .assertTrue ( 167all ( 168"/beta/v1/" in r ["path" ] 169and r ["authorization" ]== "Bearer beta-1" 170for r in beta 171 ) 172 ) 173self .assertTrue ( 174any (e ["kind" ]== "user" for e in self .history (app ,second )) 175 ) 176third = self .default_session (app ) 177app .prompt (third ,"hang then recover" ).close () 178ready = app .workspace / "recovery-started" 179deadline = time .monotonic ()+ 15 180while not ready .exists ()and time .monotonic ()< deadline : 181time .sleep (0.05 ) 182self .assertTrue (ready .exists (),"recovery tool did not start" ) 183stamp = (app .workspace / "example.txt" ).stat ().st_mtime_ns 184self .settings ( 185app ,protocol ,active = "alpha" ,beta = "changed-beta-default" 186 ) 187config = json .loads ((app .home / "config.json" ).read_text ()) 188config ["providers" ]["beta" ]["apiKey" ]= "beta-2" 189 (app .home / "config.json" ).write_text (json .dumps (config )) 190start = len (self .provider .requests ) 191self .restart (app ) 192app .idle (third ) 193restored = self .history (app ,third ) 194self .assertTrue ( 195any ( 196e ["kind" ]== "assistant" and self .entry_text (e )== "finished" 197for e in restored 198 ), 199 { 200"history" :restored , 201"session" :self .read (app ,f"/sessions/ { third } ?tail=0" ), 202 }, 203 ) 204resumed = self .records (start ) 205self .assertTrue (resumed ) 206self .assertTrue ( 207all ( 208"/beta/v1/" in r ["path" ] 209and r ["authorization" ]== "Bearer beta-2" 210and r ["model" ]== "fixture-beta" 211for r in resumed 212 ) 213 ) 214inputs = resumed [0 ]["request" ][ 215"messages" if protocol == "chat_completions" else "input" 216 ] 217note = [contents (i )for i in inputs if i .get ("role" )== "user" ][- 1 ] 218self .assertTrue ( 219note .startswith ( 220'<system-note origin="daemon restart">albedo restarted' 221 ) 222 ) 223# The detached kernel outlives the crash: it reattached with 224# its namespace, so the notice says that, not a reset. 225self .assertIn ("<system-note>The python kernel reattached" ,note ) 226self .assertNotIn ("<system-note>The python kernel got reset" ,note ) 227markers = [ 228e 229for e in restored 230if e ["kind" ]== "note" and "albedo restarted" in self .entry_text (e ) 231 ] 232self .assertEqual (len (markers ),1 ) 233self .assertTrue ( 234self .entry_text (markers [0 ]).startswith ("albedo restarted" ) 235 ) 236sessions = { 237s ["id" ]:s for s in self .read (app ,"/sessions?scope=all" )["items" ] 238 } 239self .assertEqual ( 240 ( 241sessions [session ]["provider_profile" ], 242sessions [session ]["automatic_name" ], 243 ), 244 ("alpha" ,"write and inspect a file" ), 245 ) 246self .assertEqual ( 247 ( 248sessions [third ]["provider_profile" ], 249sessions [third ]["automatic_name" ], 250 ), 251 ("beta" ,"hang then recover" ), 252 ) 253self .assertEqual ( 254 (app .workspace / "example.txt" ).stat ().st_mtime_ns ,stamp 255 ) 256self .assertTrue ( 257any (e ["kind" ]== "assistant" for e in self .history (app ,session )) 258 )