okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
e9df547
main
1//// Account quota, capacity routing, and exact pool ordering require provider 2//// limit states and captured clocks the E2E fake provider cannot reproduce. 3 4import albedo/clock 5import albedo/harness/accounts 6import albedo/harness/extension as harness_extension 7import albedo/harness/extensions/alibaba/extension as alibaba 8import albedo/harness/extensions/antigravity/extension as antigravity 9import albedo/harness/oauth 10import albedo/harness/rotation 11import albedo/openai_api 12import albedo/openai_api/types 13import gleam/dict .{ type Dict } 14import gleam/dynamic .{ type Dynamic } 15import gleam/erlang/process 16import gleam/int 17import gleam/list 18import gleam/option .{ None , Some } 19import gleam/string 20import gleeunit/should 21 22const google_accounts = 23"{\"accounts\":{\"google-antigravity\":[{\"type\":\"oauth\",\"access\":\"a1\",\"refresh\":\"r1\",\"expires\":9999999999999,\"projectId\":\"p1\",\"email\":\"one@example.com\"},{\"type\":\"oauth\",\"access\":\"a2\",\"refresh\":\"r2\",\"expires\":9999999999999,\"projectId\":\"p2\",\"email\":\"two@example.com\"}]}}" 24 25const claude_quota = 26"{\"error\":{\"code\":429,\"status\":\"RESOURCE_EXHAUSTED\",\"message\":\"You have exhausted your capacity on this model.\",\"details\":[{\"@type\":\"type.googleapis.com/google.rpc.ErrorInfo\",\"reason\":\"QUOTA_EXHAUSTED\",\"metadata\":{\"model\":\"claude-sonnet-5-5-high\",\"quotaResetDelay\":\"2h3m4.5s\"}},{\"@type\":\"type.googleapis.com/google.rpc.RetryInfo\",\"retryDelay\":\"7384.5s\"}]}}" 27 28const gemini_quota = 29"{\"error\":{\"code\":429,\"status\":\"RESOURCE_EXHAUSTED\",\"message\":\"You have exhausted your capacity on this model.\",\"details\":[{\"@type\":\"type.googleapis.com/google.rpc.ErrorInfo\",\"reason\":\"QUOTA_EXHAUSTED\",\"metadata\":{\"model\":\"gemini-3.8-flash-medium\",\"quotaResetDelay\":\"2h3m4.5s\"}},{\"@type\":\"type.googleapis.com/google.rpc.RetryInfo\",\"retryDelay\":\"7384.5s\"}]}}" 30 31const quota = 32"{\"error\":{\"code\":429,\"status\":\"RESOURCE_EXHAUSTED\",\"message\":\"You have exhausted your capacity on this model.\",\"details\":[{\"@type\":\"type.googleapis.com/google.rpc.ErrorInfo\",\"reason\":\"QUOTA_EXHAUSTED\",\"metadata\":{\"quotaResetDelay\":\"2h3m4.5s\"}},{\"@type\":\"type.googleapis.com/google.rpc.RetryInfo\",\"retryDelay\":\"7384.5s\"}]}}" 33 34const capacity = 35"{\"error\":{\"code\":429,\"status\":\"RESOURCE_EXHAUSTED\",\"message\":\"No capacity available for model\",\"details\":[{\"@type\":\"type.googleapis.com/google.rpc.ErrorInfo\",\"reason\":\"MODEL_CAPACITY_EXHAUSTED\"}]}}" 36 37fn request () -> types . Request { 38openai_api . request ( "gemini-3-flash" , [ types . User ( "hi" )]) 39} 40 41/// Answers 429 with `body` for the listed accounts and fails plainly for any 42/// other, recording each account tried. 43fn limited_stream ( 44limited : List ( String ), 45body : String , 46tried : process . Subject ( String ), 47key : fn ( account) -> String , 48) -> fn ( account, a, b) -> Result ( c, types . Error ) { 49fn ( account , _request , _on_event ) { 50process . send ( tried , key ( account )) 51case list . contains ( limited , key ( account )) { 52True -> Error ( types . HttpError ( 429 , body )) 53False -> Error ( types . HttpError ( 500 , "sibling answered" )) 54} 55} 56} 57 58fn drain ( tried : process . Subject ( String ), found : List ( String )) -> List ( String ) { 59case process . receive ( tried , 0 ) { 60Ok ( token ) -> drain ( tried , [ token , .. found ]) 61Error ( _ ) -> list . reverse ( found ) 62} 63} 64 65fn no_wait ( _ : Int ) -> Nil { 66panic as "a sibling had room; nothing should wait" 67} 68 69fn token ( access : antigravity . Access ) -> String { 70access . token 71} 72 73pub fn antigravity_sessions_spread_and_stick_test () -> Nil { 74let #( root , _ , home ) = fixture () 75let _ = write ( home , "creds.json" , google_accounts ) 76let pool = fn ( session ) { 77antigravity . pool ( home , session , fn ( _ , _ , _ ) { Error ( types . Timeout ) }) 78} 79let assert Ok ( first ) = { pool ( "session-1" ) }. current () 80let assert Ok ( again ) = { pool ( "session-1" ) }. current () 81again |> should . equal ( first ) 82let assert Ok ( other ) = { pool ( "session-2" ) }. current () 83{ other . token == first . token } |> should . be_false 84cleanup ( root ) 85} 86 87pub fn antigravity_quota_moves_the_request_to_a_sibling_test () -> Nil { 88let #( root , _ , home ) = fixture () 89let _ = write ( home , "creds.json" , google_accounts ) 90let tried = process . new_subject () 91let pool = 92antigravity . pool ( 93home , 94"quota-session" , 95limited_stream ([ "a1" , "a2" ], quota , tried , token ), 96) 97let assert Ok ( first ) = pool . current () 98rotation . stream ( 99pool , 100first , 101request (), 102fn ( _ ) { types . Continue }, 103rotation . Budget ( 8 , 0 , fn ( _ ) { 104panic as "a spent quota lasts hours; it must not be waited on" 105}), 106fn ( _ ) { Nil }, 107) 108|> should . equal ( Error ( types . HttpError ( 429 , quota ))) 109list . length ( drain ( tried , [])) |> should . equal ( 2 ) 110 111// Both accounts show their limit in /login, and the failure says so. 112let assert [ one , two ] = oauth . accounts ( home , antigravity . login ( ())) 113string . contains ( one . detail , "limited until" ) |> should . be_true 114string . contains ( two . detail , "limited until" ) |> should . be_true 115let assert Ok ( last ) = pool . current () 116let assert Some ( message ) = 117antigravity . explain ( home , last , types . HttpError ( 429 , quota )) 118string . contains ( message , "quota exhausted" ) |> should . be_true 119string . contains ( message , "no other Google account" ) |> should . be_true 120cleanup ( root ) 121} 122 123/// Claude behind Cloud Code Assist is held to Anthropic's 2000px rule, so an 124/// oversized image is fitted before the request rather than refused after it. 125pub fn antigravity_claude_quota_leaves_gemini_served_test () -> Nil { 126let #( root , _ , home ) = fixture () 127let _ = write ( home , "creds.json" , google_accounts ) 128let tried = process . new_subject () 129let claude_pool = 130antigravity . pool_for_model ( 131home , 132"claude-session" , 133"claude-sonnet-4-6" , 134limited_stream ([ "a1" ], claude_quota , tried , token ), 135) 136let assert Ok ( first ) = claude_pool . current () 137first . token |> should . equal ( "a1" ) 138 139// Account a1 hits a Claude 429 and hops to sibling a2. 140rotation . stream ( 141claude_pool , 142first , 143openai_api . request ( "claude-sonnet-4-6" , [ types . User ( "hi" )]), 144fn ( _ ) { types . Continue }, 145rotation . Budget ( 8 , 0 , no_wait ), 146fn ( _ ) { Nil }, 147) 148|> should . equal ( Error ( types . HttpError ( 500 , "sibling answered" ))) 149let attempts = drain ( tried , []) 150attempts |> should . equal ([ "a1" , "a2" ]) 151 152// Claude requests now treat a1 as limited and stick to a2. 153let assert Ok ( claude_next ) = claude_pool . current () 154claude_next . token |> should . equal ( "a2" ) 155 156// But Gemini requests on the same session/account are NOT walled: a1 still serves Gemini! 157let gemini_pool = 158antigravity . pool_for_model ( home , "gemini-1" , "gemini-3-flash" , fn ( _ , _ , _ ) { 159Error ( types . Timeout ) 160}) 161let assert Ok ( gemini_served ) = gemini_pool . current () 162gemini_served . token |> should . equal ( "a1" ) 163 164// /login shows a1 is limited specifically on Claude. 165let assert [ one , two ] = oauth . accounts ( home , antigravity . login ( ())) 166string . contains ( one . detail , "limited on Claude until" ) |> should . be_true 167string . contains ( two . detail , "limited on Claude until" ) |> should . be_false 168 169// When all accounts exhaust their Claude quota, explain names the Claude meter. 170let both_tried = process . new_subject () 171let both_claude_pool = 172antigravity . pool_for_model ( 173home , 174"both-claude-session" , 175"claude-sonnet-4-6" , 176limited_stream ([ "a1" , "a2" ], claude_quota , both_tried , token ), 177) 178let assert Ok ( c1 ) = both_claude_pool . current () 179rotation . stream ( 180both_claude_pool , 181c1 , 182openai_api . request ( "claude-sonnet-4-6" , [ types . User ( "hi" )]), 183fn ( _ ) { types . Continue }, 184rotation . Budget ( 8 , 0 , fn ( _ ) { 185panic as "a spent quota lasts hours; it must not be waited on" 186}), 187fn ( _ ) { Nil }, 188) 189|> should . equal ( Error ( types . HttpError ( 429 , claude_quota ))) 190let assert Ok ( last ) = both_claude_pool . current () 191let assert Some ( claude_msg ) = 192antigravity . explain ( home , last , types . HttpError ( 429 , claude_quota )) 193string . contains ( claude_msg , "Antigravity Claude quota exhausted" ) 194|> should . be_true 195string . contains ( claude_msg , "its claude meter" ) |> should . be_true 196string . contains ( 197claude_msg , 198"no other Google account has room on the Claude meter" , 199) 200|> should . be_true 201 202// Both a1 and a2 are now limited on Claude. When a2 also exhausts Gemini, and 203// then a1 exhausts Gemini, no accounts have room on Gemini, and refusal message names 204// the Gemini meter while mentioning sibling a2's Claude limit. 205let assert Some ( _ ) = 206antigravity . explain ( home , claude_next , types . HttpError ( 429 , gemini_quota )) 207let assert Some ( gemini_msg ) = 208antigravity . explain ( home , first , types . HttpError ( 429 , gemini_quota )) 209string . contains ( 210gemini_msg , 211"Antigravity Gemini quota exhausted for one@example.com" , 212) 213|> should . be_true 214string . contains ( gemini_msg , "its gemini meter" ) |> should . be_true 215string . contains ( 216gemini_msg , 217"no other Google account has room on the Gemini meter." , 218) 219|> should . be_true 220string . contains ( gemini_msg , "two@example.com is limited on Claude until" ) 221|> should . be_true 222 223cleanup ( root ) 224} 225 226pub fn antigravity_claude_declares_anthropic_image_limits_test () -> Nil { 227let #( root , _ , home ) = fixture () 228let _ = write ( home , "creds.json" , google_accounts ) 229let assert [ harness_extension . ModelProviderPlugin ( provider )] = 230list . filter ( antigravity . extension (). plugins , fn ( plugin ) { 231case plugin { 232harness_extension . ModelProviderPlugin ( _ ) -> True 233_ -> False 234} 235}) 236let images = fn ( model ) { 237let assert Some ( Ok ( upstream )) = 238provider . resolve ( harness_extension . ModelContext ( 239home , 240"image-session" , 241"" , 242"antigravity" , 243model , 244types . ChatCompletions , 245None , 246)) 247upstream . images 248} 249images ( "claude-sonnet-4-6" ) |> should . equal ( types . anthropic_images ) 250images ( "gemini-3-flash" ) |> should . equal ( types . any_images ) 251cleanup ( root ) 252} 253 254pub fn antigravity_hands_off_when_one_account_is_spent_test () -> Nil { 255let #( root , _ , home ) = fixture () 256let _ = write ( home , "creds.json" , google_accounts ) 257let tried = process . new_subject () 258let pool = 259antigravity . pool ( 260home , 261"handoff-session" , 262limited_stream ([ "a1" , "a2" ], quota , tried , token ), 263) 264let assert Ok ( first ) = pool . current () 265let pool = 266rotation . Pool ( 267.. pool , 268stream : limited_stream ([ first . token ], quota , tried , token ), 269) 270rotation . stream ( 271pool , 272first , 273request (), 274fn ( _ ) { types . Continue }, 275rotation . Budget ( 8 , 0 , no_wait ), 276fn ( _ ) { Nil }, 277) 278|> should . equal ( Error ( types . HttpError ( 500 , "sibling answered" ))) 279let attempts = drain ( tried , []) 280attempts |> list . length |> should . equal ( 2 ) 281list . first ( attempts ) |> should . equal ( Ok ( first . token )) 282// The session now sticks to the sibling until the quota resets. 283let assert Ok ( next ) = pool . current () 284{ next . token == first . token } |> should . be_false 285cleanup ( root ) 286} 287 288pub fn antigravity_capacity_waits_without_moving_test () -> Nil { 289let #( root , _ , home ) = fixture () 290let _ = write ( home , "creds.json" , google_accounts ) 291let tried = process . new_subject () 292let paused = process . new_subject () 293let pool = 294antigravity . pool ( 295home , 296"capacity-session" , 297limited_stream ([ "a1" , "a2" ], capacity , tried , token ), 298) 299let assert Ok ( first ) = pool . current () 300rotation . stream ( 301pool , 302first , 303request (), 304fn ( _ ) { types . Continue }, 305rotation . Budget ( 8 , 0 , fn ( delay ) { 306process . send ( paused , int . to_string ( delay )) 307}), 308fn ( _ ) { Nil }, 309) 310|> should . equal ( Error ( types . HttpError ( 429 , capacity ))) 311// Capacity is the model's, not the account's: the same account each time. 312drain ( tried , []) |> list . unique |> should . equal ([ first . token ]) 313drain ( paused , []) |> should . equal ([ "4000" , "8000" , "15000" , "30000" ]) 314cleanup ( root ) 315} 316 317fn alibaba_config ( a : String , b : String ) -> String { 318"{\"providers\":{\"ali-a\":{\"extension\":\"alibaba\",\"baseUrl\":\"http://127.0.0.1:1/a\",\"apiKey\":\"" 319<> a 320<> "\",\"protocol\":\"chat_completions\"},\"ali-b\":{\"extension\":\"alibaba\",\"baseUrl\":\"http://127.0.0.1:1/b\",\"apiKey\":\"" 321<> b 322<> "\",\"protocol\":\"chat_completions\"},\"other\":{\"extension\":\"openai\",\"baseUrl\":\"http://x\",\"apiKey\":\"not-alibaba\"}}}" 323} 324 325fn api_key ( client : types . Client ) -> String { 326client . api_key 327} 328 329pub fn alibaba_rotates_to_another_profiles_key_test () -> Nil { 330let #( root , _ , home ) = fixture () 331let first_key = "ka-" <> int . to_string ( unique ()) 332let second_key = "kb-" <> int . to_string ( unique ()) 333let _ = write ( home , "config.json" , alibaba_config ( first_key , second_key )) 334let tried = process . new_subject () 335let body = 336"{\"error\":{\"message\":\"Allocated quota exceeded, please increase your quota limit.\",\"code\":\"Throttling.AllocationQuota\"}}" 337let pool = alibaba . pool ( home , "ali-a" , "ali-session" ) 338let assert Ok ( first ) = pool . current () 339first . api_key |> should . equal ( first_key ) 340first . base_url |> should . equal ( "http://127.0.0.1:1/a" ) 341rotation . stream ( 342rotation . Pool ( 343.. pool , 344stream : limited_stream ([ first_key ], body , tried , api_key ), 345), 346first , 347openai_api . request ( "qwen3.8-max" , [ types . User ( "hi" )]), 348fn ( _ ) { types . Continue }, 349rotation . Budget ( 8 , 0 , no_wait ), 350fn ( _ ) { Nil }, 351) 352|> should . equal ( Error ( types . HttpError ( 500 , "sibling answered" ))) 353drain ( tried , []) |> should . equal ([ first_key , second_key ]) 354// The limited key waits its turn; the sibling keeps its own base url. 355let assert Ok ( next ) = pool . current () 356next . api_key |> should . equal ( second_key ) 357next . base_url |> should . equal ( "http://127.0.0.1:1/b" ) 358cleanup ( root ) 359} 360 361fn () -> antigravity . Endpoints { 362antigravity . Endpoints ( "http://127.0.0.1:1" , "http://127.0.0.1:1" , "" ) 363} 364 365@external ( erlang , "erlang" , "unique_integer" ) 366fn unique () -> Int 367 368@external ( erlang , "albedo_skills_test_support" , "fixture" ) 369fn fixture () -> #( String , String , String ) 370 371@external ( erlang , "albedo_skills_test_support" , "write" ) 372fn write ( base : String , relative : String , content : String ) -> String 373 374@external ( erlang , "albedo_skills_test_support" , "cleanup" ) 375fn cleanup ( root : String ) -> Nil 376 377pub fn account_order_keeps_selected_limited_and_tied_deadlines_distinct_test () -> Nil { 378let entries = [ 379accounts . Entry ( "limited-selected" , "a" , True , 300 ), 380accounts . Entry ( "open" , "b" , False , 0 ), 381accounts . Entry ( "selected" , "c" , True , 0 ), 382accounts . Entry ( "limited-first" , "d" , False , 200 ), 383accounts . Entry ( "limited-second" , "e" , False , 200 ), 384] 385accounts . order ( entries , "session" , Some ( "b" ), 100 ) 386|> should . equal ([ 387"selected" , 388"open" , 389"limited-first" , 390"limited-second" , 391"limited-selected" , 392]) 393accounts . order ( entries , "session" , Some ( "a" ), 100 ) 394|> should . equal ([ 395"selected" , 396"open" , 397"limited-first" , 398"limited-second" , 399"limited-selected" , 400]) 401accounts . order ([], "session" , None , 100 ) |> should . equal ([]) 402} 403 404pub fn account_spreading_preserves_fnv1a_byte_hash_test () -> Nil { 405let entries = 406list . map ([ 0 , 1 , 2 , 3 , 4 , 5 , 6 ], fn ( value ) { 407accounts . Entry ( value , int . to_string ( value ), False , 0 ) 408}) 409// FNV-1a over these bytes is 0x4F9F2CAB, including 32-bit overflow. 410accounts . order ( entries , "hello" , None , 0 ) 411|> should . equal ([ 2 , 3 , 4 , 5 , 6 , 0 , 1 ]) 412} 413 414@external ( erlang , "albedo_accounts" , "order" ) 415fn native_order ( 416scope : String , 417values : List ( Dict ( String , Dynamic )), 418session : String , 419identity : fn ( Dict ( String , Dynamic )) -> String , 420) -> List ( Dict ( String , Dynamic )) 421 422@external ( erlang , "albedo_accounts" , "remember" ) 423fn remember ( scope : String , session : String , identity : String ) -> Dynamic 424 425pub fn native_order_only_reads_identity_for_usable_sticky_accounts_test () -> Nil { 426let account = dict . from_list ([#( "access" , dynamic . string ( "unread token" ))]) 427let no_identity = fn ( _ ) { 428panic as "this order does not need account identity" 429} 430native_order ( "identity-contract" , [ account ], "unpinned" , no_identity ) 431|> should . equal ([ account ]) 432let _ = remember ( "identity-contract" , "pinned" , "chosen" ) 433let limited = 434dict . insert ( 435account , 436"limitedUntil" , 437dynamic . int ( clock . system_ms () + 1_000_000 ), 438) 439native_order ( "identity-contract" , [ limited ], "pinned" , no_identity ) 440|> should . equal ([ limited ]) 441}