okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
b82006c
main
1"""The model's web_search must use the user's preferred provider and fall 2back down the order only when one fails, and the /web-search page must 3reorder providers and turn them off for every later search. 4 5Exa is the one provider a test can stand in for: its endpoint is a setting, 6so a local fake answers it. The subscription providers have no sign-in on the 7test daemon, so each fails at once without reaching the network. 8 9The order and the Exa key are global settings, so each scenario has its own 10daemon.""" 11 12import ast 13import json 14import threading 15import unittest 16import urllib .error 17from http .server import BaseHTTPRequestHandler ,ThreadingHTTPServer 18 19from harness import Albedo ,Provider ,exclusive ,python ,text 20 21COLLECTION = "/extensions/web-search/providers" 22 23RESULTS = [ 24 { 25"url" :"https://gleam.run/news/one" , 26"title" :"Gleam one" , 27"highlights" : ["first highlight" ,"second" ], 28"publishedDate" :"2026-08-01" , 29 }, 30 {"url" :"https://gleam.run/news/one" ,"title" :"duplicate" }, 31 {"url" :"https://example.com/two" ,"title" :None ,"highlights" :None }, 32] 33 34SEARCH = ( 35"r = await web_search('latest gleam release', limit=2)\n" 36"print(repr([r.answer, [(s.title, s.url, s.snippet, s.published)" 37" for s in r.sources]]))\n" 38"print(r)" 39) 40 41FAILING = ( 42"try:\n" 43" await web_search('latest gleam release')\n" 44"except Exception as error:\n" 45" print('raised:', error)" 46) 47 48 49class FakeExa : 50"""Answers /search with RESULTS and records what each request sent.""" 51 52def __init__ (self ): 53self .requests = [] 54owner = self 55 56class Handler (BaseHTTPRequestHandler ): 57def do_POST (self ): 58body = json .loads (self .rfile .read (int (self .headers ["Content-Length" ]))) 59owner .requests .append ((self .path ,self .headers ["x-api-key" ],body )) 60payload = json .dumps ({"results" :RESULTS }).encode () 61self .send_response (200 ) 62self .send_header ("Content-Type" ,"application/json" ) 63self .send_header ("Content-Length" ,str (len (payload ))) 64self .end_headers () 65self .wfile .write (payload ) 66 67def log_message (self ,format ,* args ): 68pass 69 70self .server = ThreadingHTTPServer (("127.0.0.1" ,0 ),Handler ) 71self .url = f"http://127.0.0.1: { self . server . server_address [ 1 ] } " 72threading .Thread (target = self .server .serve_forever ,daemon = True ).start () 73 74def close (self ): 75self .server .shutdown () 76self .server .server_close () 77 78 79def scripting (cells ): 80"""Answers each user turn with the next scripted python cell.""" 81 82def reply (request ): 83if request ["messages" ][- 1 ].get ("role" )== "user" and cells : 84return python (cells .pop (0 )) 85return text ("done" ) 86 87return Provider (reply ) 88 89 90def last_output (app ,session ): 91results = [ 92json .loads (part ["value" ]) 93for entry in app .history (session )["items" ] 94if entry ["kind" ]== "tool_result" and entry ["tool" ]["name" ]== "python" 95for part in entry ["content" ] 96if part ["kind" ]== "json" and part ["field" ]== "result" 97 ] 98return results [- 1 ]["output" ] 99 100 101@exclusive 102class WebSearchTests (unittest .TestCase ): 103def setUp (self ): 104self .exa = FakeExa () 105self .addCleanup (self .exa .close ) 106 107def start (self ,cells ): 108provider = scripting (cells ) 109self .addCleanup (provider .close ) 110 111def prepare (app ): 112app .write_extensions ({"exa" : {"endpoint" :self .exa .url }}) 113app .store_secrets ("exa" , {"apiKey" :"exa-key" }) 114 115app = Albedo (provider ,prepare = prepare ) 116app .__enter__ () 117self .addCleanup (app .__exit__ ,None ,None ,None ) 118return app 119 120def ask (self ,app ,prompt ): 121session = app .session () 122app .prompt (session ,prompt ).close () 123app .idle (session ) 124return session ,last_output (app ,session ) 125 126def providers (self ,app ): 127with app .api (COLLECTION )as response : 128page = json .load (response ) 129return [item ["value" ]for item in page ["items" ]],page ["page" ] 130 131def change (self ,app ,name ,change ): 132with app .api (f" { COLLECTION } / { name } " , {"change" :change })as response : 133return json .load (response )["resource" ]["value" ] 134 135def patch (self ,app ,route ,body ): 136with app .api (route )as response : 137etag = response .headers ["ETag" ] 138with app .api ( 139route ,body ,method = "PATCH" ,headers = {"If-Match" :etag } 140 )as response : 141return json .load (response ) 142 143def test_session_provider_selection_is_loaded_and_isolated (self ): 144app = self .start ([FAILING ,SEARCH ,SEARCH ]) 145disabled = app .session () 146self .patch ( 147app , 148f"/sessions/ { disabled } ?view=configuration" , 149 {"selection" : {"extensions" : {"exa" :False }}}, 150 ) 151with app .api (f"/sessions/ { disabled } /reload" , {"target" :"session" })as response : 152self .assertEqual (json .load (response )["session" ]["state" ],"applied" ) 153app .prompt (disabled ,"search without exa" ).close () 154app .idle (disabled ) 155self .assertIn ("raised:" ,last_output (app ,disabled )) 156self .assertEqual (self .exa .requests , []) 157self .ask (app ,"another session can use exa" ) 158self .assertEqual (len (self .exa .requests ),1 ) 159self .patch ( 160app , 161f"/sessions/ { disabled } ?view=configuration" , 162 {"selection" : {"extensions" : {"exa" :True }}}, 163 ) 164with app .api (f"/sessions/ { disabled } /reload" , {"target" :"session" })as response : 165self .assertEqual (json .load (response )["session" ]["state" ],"applied" ) 166app .prompt (disabled ,"search after applying exa" ).close () 167app .idle (disabled ) 168self .assertEqual (len (self .exa .requests ),2 ) 169 170def test_global_eligibility_preserves_hidden_provider_preferences (self ): 171app = self .start ([]) 172self .change (app ,"exa" ,"up" ) 173self .change (app ,"exa" ,"toggle" ) 174before = json .loads ((app .home / "extensions.json" ).read_text ())["web-search" ] 175self .patch (app ,"/settings?group=extensions" , {"defaults" : {"exa" :False }}) 176self .assertNotIn ("exa" , [entry ["name" ]for entry in self .providers (app )[0 ]]) 177self .change (app ,"codex" ,"toggle" ) 178saved = json .loads ((app .home / "extensions.json" ).read_text ())["web-search" ] 179self .assertEqual (saved ["order" ],before ["order" ]) 180self .assertIn ("exa" ,saved ["off" ]) 181self .patch (app ,"/settings?group=extensions" , {"defaults" : {"exa" :True }}) 182exa = next (entry for entry in self .providers (app )[0 ]if entry ["name" ]== "exa" ) 183self .assertFalse (exa ["enabled" ]) 184self .assertEqual (exa ["position" ],before ["order" ].index ("exa" )+ 1 ) 185 186def test_search_passes_over_signed_out_providers_to_exa (self ): 187app = self .start ([SEARCH ]) 188session ,output = self .ask (app ,"search the web" ) 189answer ,sources = ast .literal_eval (output .splitlines ()[0 ]) 190self .assertEqual (answer ,"" ) 191# One source per url, cut to the limit, highlights as the snippet. 192self .assertEqual ( 193sources , 194 [ 195 ( 196"Gleam one" , 197"https://gleam.run/news/one" , 198"first highlight … second" , 199"2026-08-01" , 200 ), 201 ("https://example.com/two" ,"https://example.com/two" ,"" ,None ), 202 ], 203 ) 204self .assertIn ("[1] Gleam one (2026-08-01)" ,output ) 205# The transcript shows the search's result beside the cell. 206 (activity ,)= [ 207activity 208for entry in app .history (session )["items" ] 209if entry ["kind" ]== "tool_result" 210for part in entry ["content" ] 211if part ["kind" ]== "trace" 212for activity in part ["trace" ]["activities" ] 213if activity ["kind" ]== "web" 214 ] 215self .assertEqual (activity ["target" ],"latest gleam release" ) 216self .assertIn ("- [Gleam one](https://gleam.run/news/one)" ,activity ["detail" ]) 217 ((path ,key ,body ),)= self .exa .requests 218self .assertEqual ((path ,key ), ("/search" ,"exa-key" )) 219self .assertEqual ( 220 (body ["query" ],body ["numResults" ]), ("latest gleam release" ,2 ) 221 ) 222 223def test_page_reorders_and_turns_off_providers (self ): 224app = self .start ([FAILING ]) 225listed ,page = self .providers (app ) 226names = [entry ["name" ]for entry in listed ] 227self .assertEqual (sorted (names ), ["antigravity" ,"claude" ,"codex" ,"exa" ]) 228self .assertEqual ([entry ["position" ]for entry in listed ], [1 ,2 ,3 ,4 ]) 229self .assertTrue (all (entry ["enabled" ]for entry in listed )) 230self .assertEqual ( 231 [row ["id" ]for row in page ["rows" ]],names ,"the page lists the same order" 232 ) 233 234index = names .index ("exa" ) 235moved = self .change (app ,"exa" ,"up" ) 236self .assertEqual (moved ["position" ],index ) 237expected = names [:index - 1 ]+ ["exa" ,names [index - 1 ]]+ names [index + 1 :] 238self .assertEqual ([entry ["name" ]for entry in self .providers (app )[0 ]],expected ) 239settings = json .loads ((app .home / "extensions.json" ).read_text ()) 240self .assertEqual (settings ["web-search" ]["order" ],expected ) 241 242# A move past the top changes nothing. 243top = expected [0 ] 244self .assertEqual (self .change (app ,top ,"up" )["position" ],1 ) 245 246self .assertFalse (self .change (app ,"exa" ,"toggle" )["enabled" ]) 247_ ,output = self .ask (app ,"search the web" ) 248self .assertIn ("raised: every web search provider failed" ,output ) 249for name in ("codex" ,"claude" ,"antigravity" ): 250self .assertIn (name + ":" ,output ) 251self .assertNotIn ("exa:" ,output ) 252self .assertEqual (self .exa .requests , [],"a provider turned off is never tried" ) 253 254for name ,body ,status in ( 255 ("nobody" , {"change" :"up" },404 ), 256 ("exa" , {"change" :"sideways" },400 ), 257 ): 258with self .subTest (name = name ,body = body ): 259with self .assertRaises (urllib .error .HTTPError )as refused : 260app .api (f" { COLLECTION } / { name } " ,body ).close () 261refused .exception .close () 262self .assertEqual (refused .exception .code ,status ) 263 264 265if __name__ == "__main__" : 266unittest .main ()