okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
61c43ca
main
1"""OpenAI-compatible proxy enablement, model routing, streaming, and state projection.""" 2 3import json 4import unittest 5import urllib .error 6import urllib .request 7 8from daemon_test import delayed_request ,refused_request 9from harness import Albedo ,Provider ,exclusive ,Reply 10 11TOOLS = [ 12 {"type" :"function" ,"function" : {"name" :"bash" ,"parameters" : {"type" :"object" }}} 13] 14OPENING = [ 15 {"role" :"system" ,"content" :"be brief" }, 16 {"role" :"user" ,"content" :"list files" }, 17] 18CALL = { 19"id" :"call-1" , 20"type" :"function" , 21"function" : {"name" :"bash" ,"arguments" :'{"command":"ls"}' }, 22} 23HISTORY = OPENING + [ 24 {"role" :"assistant" ,"content" :"hel" ,"tool_calls" : [CALL ]}, 25 {"role" :"tool" ,"tool_call_id" :"call-1" ,"content" :"a.txt" }, 26] 27 28 29def fixture (request ): 30if "messages" in request : 31if request ["messages" ][- 1 ]["role" ]== "tool" : 32deltas = [({"role" :"assistant" ,"content" :"ran it" },None ), ({},"stop" )] 33else : 34deltas = [ 35 ({"role" :"assistant" ,"reasoning_content" :"hmm" },None ), 36 ({"content" :"hel" },None ), 37 ( 38 { 39"tool_calls" : [ 40 { 41"index" :0 , 42** CALL , 43"function" : { 44"name" :"bash" , 45"arguments" :'{"command":' , 46 }, 47 } 48 ] 49 }, 50None , 51 ), 52 ( 53 {"tool_calls" : [{"index" :0 ,"function" : {"arguments" :'"ls"}' }}]}, 54None , 55 ), 56 ({},"tool_calls" ), 57 ] 58events = [ 59 { 60"id" :"c" , 61"choices" : [{"index" :0 ,"delta" :delta ,"finish_reason" :reason }], 62** ( 63 {"usage" : {"prompt_tokens" :7 ,"completion_tokens" :3 }} 64if reason == "tool_calls" 65else {} 66 ), 67 } 68for delta ,reason in deltas 69 ] 70else : 71last = request ["input" ][- 1 ] 72if last .get ("role" )== "user" and last .get ("content" )== "call a tool" : 73output = [ 74 { 75"type" :"reasoning" , 76"id" :"rs_1" , 77"summary" : [], 78"encrypted_content" :"sealed-reasoning" , 79 }, 80 { 81"type" :"function_call" , 82"id" :"fc_1" , 83"call_id" :"call_r1" , 84"name" :"bash" , 85"arguments" :'{"command":"ls"}' , 86"status" :"completed" , 87 }, 88 ] 89else : 90output = [ 91 { 92"type" :"message" , 93"role" :"assistant" , 94"status" :"completed" , 95"content" : [ 96 {"type" :"output_text" ,"text" :"done" ,"annotations" : []} 97 ], 98 } 99 ] 100events = [ 101 { 102"type" :"response.completed" , 103"response" : { 104"id" :"r" , 105"status" :"completed" , 106"output" :output , 107"usage" : {"input_tokens" :5 ,"output_tokens" :1 }, 108 }, 109 } 110 ] 111return Reply ("raw" ,events = events ) 112 113 114class ProxyTests (unittest .TestCase ): 115def setUp (self ): 116self .provider = Provider (fixture ) 117self .addCleanup (self .provider .close ) 118endpoint = self .provider .url + "/v1" 119self .chat = f"chat- { self . provider . route } " 120self .resp = f"resp- { self . provider . route } " 121self .cx = f"cx- { self . provider . route } " 122self .broken = f"broken- { self . provider . route } " 123profiles = { 124self .chat : { 125"baseUrl" :endpoint , 126"apiKey" :"k" , 127"model" :"m-chat" , 128"protocol" :"chat_completions" , 129 }, 130self .resp : { 131"baseUrl" :endpoint , 132"apiKey" :"k" , 133"model" :"m-resp" , 134"protocol" :"responses" , 135 }, 136self .cx : { 137"extension" :"codex" , 138"model" :"gpt-saved" , 139"protocol" :"responses" , 140 }, 141self .broken : {"model" :3 }, 142 } 143 144self .app = Albedo (self .provider ,providers = profiles ) 145self .app .__enter__ () 146self .addCleanup (self .app .__exit__ ,None ,None ,None ) 147self .base = self .app .base + "/extensions/proxy/v1" 148 149def get_models (self ): 150return urllib .request .urlopen ( 151urllib .request .Request ( 152self .base + "/models" , 153headers = {"Authorization" :"Bearer " + self .app .connection ["token" ]}, 154 ), 155timeout = 10 , 156 ) 157 158def enable (self ): 159self .app .write_extensions ({"enabled" : {"proxy" :True }}) 160 161def post (self ,body ,* ,headers = None ): 162request = urllib .request .Request ( 163self .base + "/chat/completions" , 164json .dumps (body ).encode (), 165 { 166"Content-Type" :"application/json" , 167"Authorization" :"Bearer " + self .app .connection ["token" ], 168** (headers or {}), 169 }, 170 ) 171return urllib .request .urlopen (request ,timeout = 30 ) 172 173def sent (self ): 174record = self .provider .requests [- 1 ] 175return record ["path" ],record ["request" ] 176 177# exclusive: toggles the global proxy gateway enablement 178@exclusive 179def test_enablement_listing_and_profile_errors (self ): 180self .app .write_extensions ({"enabled" : {"proxy" :False }}) 181with self .assertRaises (urllib .error .HTTPError )as rejected : 182self .get_models () 183self .assertEqual (rejected .exception .code ,404 ) 184self .enable () 185with self .get_models ()as response : 186listing = json .load (response ) 187self .assertTrue ( 188 {f" { self . chat } /m-chat" ,f" { self . resp } /m-resp" ,f" { self . cx } /gpt-saved" } 189<= {model ["id" ]for model in listing ["data" ]} 190 ) 191self .assertIn (self .broken , [error ["profile" ]for error in listing ["errors" ]]) 192with self .post ( 193 { 194"model" :f" { self . chat } /unlisted" , 195"messages" : [{"role" :"user" ,"content" :"hi" }], 196 } 197 )as response : 198self .assertEqual (json .load (response )["model" ],f" { self . chat } /unlisted" ) 199self .assertEqual (self .sent ()[1 ]["model" ],"unlisted" ) 200self .assertEqual (self .provider .requests [- 1 ]["authorization" ],"Bearer k" ) 201for model in (self .broken ,f" { self . cx } /gpt-saved" ): 202with ( 203self .subTest (model = model ), 204self .assertRaises (urllib .error .HTTPError )as rejected , 205 ): 206self .post ( 207 {"model" :model ,"messages" : [{"role" :"user" ,"content" :"hi" }]} 208 ) 209self .assertEqual (rejected .exception .code ,400 ) 210self .assertIn ("/login" ,json .load (rejected .exception )["error" ]["message" ]) 211with self .assertRaises (urllib .error .HTTPError )as rejected : 212self .post ({},headers = {"Origin" :"https://example.com" }) 213self .assertEqual (rejected .exception .code ,403 ) 214with self .assertRaises (urllib .error .HTTPError )as rejected : 215self .post ({"model" :"missing/x" ,"messages" :OPENING }) 216self .assertEqual (rejected .exception .code ,400 ) 217 218def test_ingress_limits_and_origin_refuse_before_provider_work (self ): 219path = "/extensions/proxy/v1/chat/completions" 220for framing ,expected_status ,code in ( 221 ({"Transfer-Encoding" :"identity" },400 ,"unsupported_transfer_encoding" ), 222 ({"Content-Length" :"invalid" },400 ,"invalid_request" ), 223 ({"Content-Length" :"32000001" },413 ,"request_body_too_large" ), 224 ): 225with self .subTest (framing = framing ): 226status ,headers ,body = refused_request ( 227self .app , 228path , 229 { 230** framing , 231"Authorization" :"Bearer " + self .app .connection ["token" ], 232 }, 233method = "POST" , 234 ) 235self .assertEqual (status ,expected_status ) 236self .assertEqual (json .loads (body )["error" ]["code" ],code ) 237status ,_ ,_ = refused_request ( 238self .app , 239path , 240 { 241** framing , 242"Origin" :"https://attacker.example" , 243"Authorization" :"Bearer " + self .app .connection ["token" ], 244 }, 245method = "POST" , 246 ) 247self .assertEqual (status ,403 ) 248self .assertFalse (self .provider .requests ) 249 250def test_authentication_precedes_body_and_authenticated_unknown_routes_keep_connection_usable ( 251self , 252 ): 253for method ,path ,expected_status in ( 254 ("GET" ,"/extensions/proxy/v1/models" ,401 ), 255 ("GET" ,"/extensions/proxy/v1/missing" ,401 ), 256 ("POST" ,"/extensions/proxy/v1/missing" ,401 ), 257 ): 258with self .subTest (method = method ,path = path ): 259status ,headers ,_ = refused_request ( 260self .app ,path , {"Content-Length" :"2" },method = method 261 ) 262self .assertEqual (status ,expected_status ) 263self .assertEqual (headers ["www-authenticate" ],"Bearer" ) 264if expected_status == 401 : 265self .assertEqual ( 266delayed_request ( 267self .app , 268method , 269path , 270b"{}" , 271 {"Authorization" :"Bearer " + self .app .connection ["token" ]}, 272 ), 273 (200 if path .endswith ("/models" )else 404 ,200 ), 274 ) 275 276@exclusive 277def test_anonymous_proxy_requires_explicit_operator_opt_in (self ): 278self .enable () 279with self .assertRaises (urllib .error .HTTPError )as rejected : 280urllib .request .urlopen (self .base + "/models" ,timeout = 10 ) 281self .assertEqual (rejected .exception .code ,401 ) 282with self .get_models ()as response : 283self .assertEqual (response .status ,200 ) 284previous = self .app .env .get ("ALBEDO_PROXY_ALLOW_ANONYMOUS" ) 285try : 286self .app .env ["ALBEDO_PROXY_ALLOW_ANONYMOUS" ]= "1" 287self .app .restart () 288self .base = self .app .base + "/extensions/proxy/v1" 289with urllib .request .urlopen (self .base + "/models" ,timeout = 10 )as response : 290self .assertEqual (response .status ,200 ) 291for route in ( 292"/server" , 293"/extensions/work/items" , 294"/extensions/proxy/v1/missing" , 295 ): 296with ( 297self .subTest (route = route ), 298self .assertRaises (urllib .error .HTTPError )as rejected , 299 ): 300urllib .request .urlopen (self .app .base + route ,timeout = 10 ) 301self .assertEqual (rejected .exception .code ,401 ) 302finally : 303if previous is None : 304self .app .env .pop ("ALBEDO_PROXY_ALLOW_ANONYMOUS" ,None ) 305else : 306self .app .env ["ALBEDO_PROXY_ALLOW_ANONYMOUS" ]= previous 307self .app .restart () 308self .base = self .app .base + "/extensions/proxy/v1" 309 310def test_streamed_chat_deltas_usage_and_provider_routing (self ): 311with self .post ( 312 { 313"model" :self .chat , 314"messages" :OPENING , 315"tools" :TOOLS , 316"stream" :True , 317"stream_options" : {"include_usage" :True }, 318 } 319 )as response : 320self .assertTrue ( 321response .headers ["content-type" ].startswith ("text/event-stream" ) 322 ) 323lines = [ 324line [6 :] 325for line in response .read ().decode ().splitlines () 326if line .startswith ("data: " ) 327 ] 328self .assertEqual (lines [- 1 ],"[DONE]" ) 329stream = [json .loads (line )for line in lines [:- 1 ]] 330deltas = [event ["choices" ][0 ]["delta" ]for event in stream if event ["choices" ]] 331self .assertEqual (deltas [0 ]["role" ],"assistant" ) 332self .assertEqual ("" .join (delta .get ("content" ,"" )for delta in deltas ),"hel" ) 333self .assertEqual ( 334"" .join (delta .get ("reasoning_content" ,"" )for delta in deltas ),"hmm" 335 ) 336 [call ]= [call for delta in deltas for call in delta .get ("tool_calls" , [])] 337self .assertTrue (call ["id" ].startswith ("call-1__albedo__" )) 338self .assertEqual (call ["function" ],CALL ["function" ]) 339self .assertEqual ( 340 [ 341event ["choices" ][0 ]["finish_reason" ] 342for event in stream 343if event ["choices" ] 344 ][- 1 ], 345"tool_calls" , 346 ) 347self .assertEqual (stream [- 1 ]["usage" ]["prompt_tokens" ],7 ) 348self .assertEqual (stream [- 1 ]["choices" ], []) 349self .assertTrue (all (event ["model" ]== self .chat for event in stream )) 350path ,sent = self .sent () 351self .assertTrue (path .endswith ("/v1/chat/completions" ),path ) 352self .assertEqual (sent ["model" ],"m-chat" ) 353self .assertEqual (sent ["messages" ][0 ],OPENING [0 ]) 354 355def test_chat_and_responses_history_projection (self ): 356for model in (f" { self . chat } /m-chat" ,f" { self . resp } /m-resp" ): 357with self .subTest (model = model ): 358with self .post ( 359 {"model" :model ,"messages" :HISTORY ,"tools" :TOOLS } 360 )as response : 361completion = json .load (response ) 362self .assertEqual (completion ["object" ],"chat.completion" ) 363self .assertEqual (completion ["choices" ][0 ]["finish_reason" ],"stop" ) 364path ,sent = self .sent () 365if model .startswith (self .chat ): 366self .assertEqual ( 367 [message ["role" ]for message in sent ["messages" ]], 368 ["system" ,"user" ,"assistant" ,"tool" ], 369 ) 370self .assertEqual ( 371sent ["messages" ][2 ]["tool_calls" ][0 ]["id" ],"call-1" 372 ) 373else : 374self .assertTrue (path .endswith ("/v1/responses" ),path ) 375self .assertEqual (sent ["instructions" ],"be brief" ) 376self .assertEqual ( 377 [item .get ("type" ,item .get ("role" ))for item in sent ["input" ]], 378 ["user" ,"assistant" ,"function_call" ,"function_call_output" ], 379 ) 380self .assertEqual ( 381sent ["input" ][3 ], 382 { 383"type" :"function_call_output" , 384"call_id" :"call-1" , 385"output" :"a.txt" , 386 }, 387 ) 388 389def test_response_state_round_trips_only_to_originating_profile (self ): 390ask = [{"role" :"user" ,"content" :"call a tool" }] 391with self .post ( 392 {"model" :self .resp ,"messages" :ask ,"tools" :TOOLS } 393 )as response : 394first = json .load (response )["choices" ][0 ]["message" ] 395 [call ]= first ["tool_calls" ] 396self .assertTrue (call ["id" ].startswith ("call_r1__albedo__" )) 397echoed = ask + [ 398 {"role" :"assistant" ,"content" :None ,"tool_calls" : [call ]}, 399 {"role" :"tool" ,"tool_call_id" :call ["id" ],"content" :"a.txt" }, 400 ] 401with self .post ({"model" :self .resp ,"messages" :echoed ,"tools" :TOOLS }): 402pass 403sent = self .sent ()[1 ] 404self .assertEqual ( 405sent ["input" ][1 ], 406 { 407"type" :"reasoning" , 408"id" :"rs_1" , 409"summary" : [], 410"encrypted_content" :"sealed-reasoning" , 411 }, 412 ) 413self .assertEqual ( 414 [sent ["input" ][index ]["call_id" ]for index in (2 ,3 )], ["call_r1" ]* 2 415 ) 416with self .post ({"model" :self .chat ,"messages" :echoed ,"tools" :TOOLS }): 417pass 418path ,sent = self .sent () 419self .assertTrue (path .endswith ("/v1/chat/completions" ),path ) 420self .assertNotIn ("sealed-reasoning" ,json .dumps (sent )) 421self .assertEqual (sent ["messages" ][1 ]["tool_calls" ][0 ]["id" ],"call_r1" ) 422self .assertEqual (sent ["messages" ][2 ]["tool_call_id" ],"call_r1" )