okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
cba5ff8
main
1"""Durable transcript content, checkpoint forks, and retained execution traces.""" 2 3import json 4import urllib .error 5import urllib .parse 6 7from harness import exclusive ,operation_id 8 9from integration_support import IntegrationScenario 10 11 12class HistoryContentTests (IntegrationScenario ): 13# exclusive: restarts the daemon 14@exclusive 15def test_tool_checkpoint_forks_without_executing_its_pending_call (self ): 16for protocol in ("responses" ,"chat_completions" ): 17with self .subTest (protocol = protocol ): 18app = self .app_for (protocol ) 19unrelated = app .session () 20self .send (app ,unrelated ,"continue from branch" ) 21session = app .session () 22message = 'write and inspect a file: "\\\n' + "😀e\u0301漢" * 400 23self .send (app ,session ,message ) 24entries = self .history (app ,session ) 25preview = self .read (app ,f"/sessions/ { session } ?tail=0" )["preview" ] 26self .assertEqual (preview ["text" ],message [:256 ]) 27self .assertLessEqual (len (preview ["text" ]),256 ) 28self .assertLessEqual (len (preview ["text" ].encode ("utf-8" )),1024 ) 29self .assertTrue (preview ["truncated" ]) 30self .assertEqual ( 31preview ["transcript_count" ], 32len ( 33 { 34entry ["position" ] 35for entry in entries 36if entry ["kind" ]!= "continuation" 37 } 38 ), 39 ) 40summary = next ( 41item 42for item in self .read (app ,"/sessions?scope=all" )["items" ] 43if item ["id" ]== session 44 ) 45self .assertEqual (summary ["preview" ],preview ) 46self .assertEqual ((app .workspace / "example.txt" ).read_text (),"hello\n" ) 47self .assertTrue ( 48any ( 49e ["kind" ]== "assistant" and self .entry_text (e )== "finished" 50for e in entries 51 ) 52 ) 53self .assertTrue ( 54any ( 55e ["kind" ]== "thinking" 56and self .entry_text (e )== "reasoning about the task" 57for e in entries 58 ) 59 ) 60tool = next (e for e in entries if e ["kind" ]== "tool_result" ) 61result = next ( 62part ["value" ] 63for part in tool ["content" ] 64if part ["kind" ]== "json" and part ["field" ]== "result" 65 ) 66trace = next ( 67part ["trace" ]for part in tool ["content" ]if part ["kind" ]== "trace" 68 ) 69self .assertEqual (json .loads (result )["status" ],"ok" ) 70self .assertTrue ( 71any ( 72a ["kind" ]== "read" and a ["target" ].endswith ("example.txt" ) 73for a in trace ["activities" ] 74 ) 75 ) 76self .assertTrue ( 77any (c ["path" ].endswith ("example.txt" )for c in trace ["changes" ]) 78 ) 79checkpoint = next (e for e in entries if e ["kind" ]== "tool_call" ) 80result_page = self .read ( 81app , 82f"/sessions/ { session } /history?after= { checkpoint [ 'position' ] } &limit=1" , 83 ) 84self .assertFalse ( 85any (entry ["kind" ]== "tool_call" for entry in result_page ["items" ]) 86 ) 87result_first = next ( 88entry 89for entry in result_page ["items" ] 90if entry ["kind" ]== "tool_result" 91 ) 92self .assertEqual (result_first ["tool" ]["name" ],"python" ) 93self .assertEqual ( 94result_first ["tool" ]["tool_call_id" ], 95checkpoint ["tool" ]["tool_call_id" ], 96 ) 97self .assertEqual (result_first ["turn_id" ],checkpoint ["turn_id" ]) 98branch_id = operation_id () 99with app .api ( 100f"/sessions/ { branch_id } " , 101 { 102"kind" :"fork" , 103"source_session_id" :session , 104"checkpoint_id" :checkpoint ["checkpoint_id" ], 105 }, 106method = "PUT" , 107headers = {"If-None-Match" :"*" }, 108 )as response : 109branch = json .load (response ) 110self .assertEqual ( 111 (branch ["provider_profile" ],branch ["model" ]), 112 (app .profile ,"fixture-model" ), 113 ) 114self .assertEqual ( 115branch ["creation" ]["resolved" ], 116 { 117key :branch [key ] 118for key in ("workspace" ,"provider_profile" ,"model" ,"effort" ) 119 }, 120 ) 121branch_entries = self .history (app ,branch_id ) 122branch_preview = self .read (app ,f"/sessions/ { branch_id } ?tail=0" )[ 123"preview" 124 ] 125self .assertEqual ( 126branch_preview ["transcript_count" ], 127len ( 128 { 129entry ["position" ] 130for entry in branch_entries 131if entry ["kind" ]!= "continuation" 132 } 133 ), 134 ) 135self .assertEqual (branch_preview ["text" ],message [:256 ]) 136self .assertTrue (branch_preview ["truncated" ]) 137pending_result = next ( 138e for e in branch_entries if e ["kind" ]== "tool_result" 139 ) 140self .assertIn ( 141"not executed after branch checkpoint" , 142json .dumps (pending_result ["content" ]), 143 ) 144self .assertEqual ( 145 [ 146 (e ["kind" ],e ["content" ],e ["turn_id" ]) 147for e in branch_entries 148if e ["id" ]!= pending_result ["id" ] 149 ], 150 [ 151 (e ["kind" ],e ["content" ],e ["turn_id" ]) 152for e in entries 153if e ["position" ]<= checkpoint ["position" ] 154 ], 155 ) 156start = len (self .provider .requests ) 157self .send (app ,branch_id ,"continue from branch" ) 158continued = self .history (app ,branch_id ) 159continued_preview = self .read (app ,f"/sessions/ { branch_id } ?tail=0" )[ 160"preview" 161 ] 162self .assertEqual ( 163continued_preview ["transcript_count" ], 164len ( 165 { 166entry ["position" ] 167for entry in continued 168if entry ["kind" ]!= "continuation" 169 } 170 ), 171 ) 172self .assertGreater ( 173len ( 174 { 175entry ["position" ] 176for entry in continued 177if entry ["kind" ]!= "continuation" 178 } 179 ), 180len ( 181 { 182entry ["position" ] 183for entry in branch_entries 184if entry ["kind" ]!= "continuation" 185 } 186 ), 187 ) 188self .assertEqual (continued_preview ["text" ],"continue from branch" ) 189self .assertFalse (continued_preview ["truncated" ]) 190requests = self .records (start ) 191self .assertEqual (len (requests ),1 ) 192self .assertIn ( 193"/chat/completions" 194if protocol == "chat_completions" 195else "/responses" , 196requests [0 ]["path" ], 197 ) 198inputs = requests [0 ]["request" ][ 199"messages" if protocol == "chat_completions" else "input" 200 ] 201outputs = [ 202i 203for i in inputs 204if i .get ("role" )== "tool" 205or i .get ("type" )== "function_call_output" 206 ] 207self .assertEqual ( 208 [i .get ("content" ,i .get ("output" ))for i in outputs ], 209 ["not executed after branch checkpoint" ], 210 ) 211self .assertEqual (self .history (app ,session ),entries ) 212 213# exclusive: restarts the daemon 214@exclusive 215def test_unicode_history_pages_preserve_content_and_timestamps (self ): 216for protocol in ("responses" ,"chat_completions" ): 217with self .subTest (protocol = protocol ): 218app = self .app_for (protocol ) 219session = app .session () 220self .send (app ,session ,"write and inspect a file" ) 221self .send (app ,session ,"review the result" ) 222timestamps = [ 223 (e ["kind" ],e ["content" ],e ["created_at" ]) 224for e in self .history (app ,session ) 225if e ["kind" ]in ("user" ,"assistant" ) 226 ] 227self .assertTrue (timestamps ) 228self .assertTrue ( 229all (isinstance (stamp ,str )for _ ,_ ,stamp in timestamps ) 230 ) 231self .restart (app ) 232self .assertEqual ( 233 [ 234 (e ["kind" ],e ["content" ],e ["created_at" ]) 235for e in self .history (app ,session ) 236if e ["kind" ]in ("user" ,"assistant" ) 237 ], 238timestamps , 239 ) 240 241large_inputs = {} 242for index in range (3 ): 243identity = operation_id () 244body = f"continue from branch { index } : " + '😀e\u0301\\"\n' * 75_000 245with app .api ( 246f"/sessions/ { session } /inputs/ { identity } " , 247 {"kind" :"message" ,"text" :body }, 248method = "PUT" , 249 )as response : 250json .load (response ) 251app .idle (session ,timeout = 90 ) 252large_inputs [identity ]= body 253 254def traverse (path ,direction ): 255collected ,tokens = [],set () 256for _ in range (100 ): 257with app .api (path )as response : 258encoded = response .read () 259self .assertLessEqual (len (encoded ),1_048_576 ) 260page = json .loads (encoded ) 261self .assertTrue ( 262page ["items" ],"history cursor failed to advance" 263 ) 264collected = ( 265page ["items" ]+ collected 266if direction == "older" 267else collected + page ["items" ] 268 ) 269token = page [direction ] 270if token is None : 271ids = [entry ["id" ]for entry in collected ] 272self .assertEqual (len (ids ),len (set (ids ))) 273return collected 274self .assertNotIn (token ,tokens ,"history cursor repeated" ) 275tokens .add (token ) 276path = f"/sessions/ { session } /history?limit=200&next= { urllib . parse . quote ( token , safe = '' ) } " 277self .fail ("history pagination did not terminate" ) 278 279backwards = traverse (f"/sessions/ { session } /history?limit=200" ,"older" ) 280forwards = traverse ( 281f"/sessions/ { session } /history?after=0&limit=200" ,"newer" 282 ) 283self .assertEqual ( 284 [entry ["id" ]for entry in forwards ], 285 [entry ["id" ]for entry in backwards ], 286 ) 287for entry in backwards : 288if entry ["input_id" ]not in large_inputs : 289continue 290references = [ 291part ["reference" ] 292for part in entry ["content" ] 293if part ["kind" ]== "reference" 294 ] 295self .assertEqual (len (references ),1 ) 296reference = references [0 ] 297pieces ,offset ,token = [],0 ,None 298for _ in range (100 ): 299path = reference ["url" ] 300if token is not None : 301path += "?" + urllib .parse .urlencode ({"next" :token }) 302content = self .read (app ,path ) 303for part in content ["parts" ]: 304self .assertEqual (part ["encoding" ],"utf8" ) 305self .assertEqual (part ["offset_bytes" ],offset ) 306pieces .append (part ["text" ]) 307offset += len (part ["text" ].encode ("utf-8" )) 308token = content ["next" ] 309if token is None : 310self .assertTrue (content ["parts" ][- 1 ]["complete" ]) 311break 312else : 313self .fail ("full content pagination did not terminate" ) 314self .assertEqual (offset ,reference ["bytes" ]) 315self .assertEqual ( 316"" .join (pieces ),large_inputs .pop (entry ["input_id" ]) 317 ) 318self .assertEqual (large_inputs , {}) 319 320def test_fork_retains_continuation_and_trace_after_source_deletion (self ): 321for protocol in ("responses" ,"chat_completions" ): 322with self .subTest (protocol = protocol ): 323app = self .app_for (protocol ) 324session = app .session () 325self .send (app ,session ,"write and inspect a file" ) 326tool = next ( 327entry 328for entry in self .history (app ,session ) 329if entry ["kind" ]== "tool_result" 330 ) 331trace = next ( 332part ["trace" ]for part in tool ["content" ]if part ["kind" ]== "trace" 333 ) 334continuation_id = operation_id () 335with app .api ( 336f"/sessions/ { session } /inputs/ { continuation_id } " , 337 {"kind" :"continue" }, 338method = "PUT" , 339 )as response : 340json .load (response ) 341app .idle (session ,timeout = 90 ) 342continuation = next ( 343entry 344for entry in self .history (app ,session ) 345if entry ["kind" ]== "continuation" 346and entry ["input_id" ]== continuation_id 347 ) 348self .send (app ,session ,"review the result" ) 349self .assertEqual ( 350self .read (app ,f"/sessions/ { session } " )["automatic_name" ], 351"review the result" , 352 ) 353backwards = self .history (app ,session ) 354trace_checkpoint = next ( 355entry 356for entry in backwards 357if entry ["kind" ]== "user" 358and self .entry_text (entry )== "review the result" 359 ) 360retained_id = operation_id () 361with app .api ( 362f"/sessions/ { retained_id } " , 363 { 364"kind" :"fork" , 365"source_session_id" :session , 366"checkpoint_id" :trace_checkpoint ["checkpoint_id" ], 367 }, 368method = "PUT" , 369headers = {"If-None-Match" :"*" }, 370 )as response : 371json .load (response ) 372 373def retained_trace (): 374history = self .history (app ,retained_id ) 375marker = next ( 376entry 377for entry in history 378if entry ["kind" ]== "continuation" 379and entry ["input_id" ]== continuation_id 380 ) 381self .assertEqual (marker ["turn_id" ],continuation ["turn_id" ]) 382self .assertEqual (marker ["content" ],continuation ["content" ]) 383result = next ( 384entry 385for entry in history 386if entry ["kind" ]== "tool_result" 387and entry ["tool" ]["tool_call_id" ] 388== tool ["tool" ]["tool_call_id" ] 389 ) 390return next ( 391part ["trace" ] 392for part in result ["content" ] 393if part ["kind" ]== "trace" 394 ) 395 396self .assertEqual (retained_trace (),trace ) 397resource = f"/sessions/ { session } ?view=configuration" 398with app .api (resource )as response : 399json .load (response ) 400revision = response .headers ["ETag" ] 401with app .api ( 402resource ,method = "DELETE" ,headers = {"If-Match" :revision } 403 )as response : 404self .assertEqual (json .load (response )["state" ],"complete" ) 405self .assertEqual (retained_trace (),trace )