okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
3b0866f
main
1// Signed Claude SSE replay, schema unions, billing hashes, and stored-credential enumeration have edge cases absent from the fake E2E provider. 2import albedo/daemon/message_content as events 3import albedo/daemon/projection 4import albedo/daemon/transcript 5import albedo/harness/extensions/claude/schema as schemas 6import albedo/harness/extensions/claude/stream 7import albedo/harness/extensions/claude/wire 8import albedo/harness/loop 9import albedo/openai_api 10import albedo/openai_api/request 11import albedo/openai_api/stream as reducer 12import albedo/openai_api/transport 13import albedo/openai_api/types 14import gleam/bit_array 15import gleam/dict 16import gleam/dynamic .{ type Dynamic } 17import gleam/dynamic/decode 18import gleam/json 19import gleam/list 20import gleam/option .{ None , Some } 21import gleam/result 22import gleam/string 23import gleam/string_tree .{ type StringTree } 24 25// 2x3 PNG header, enough for dimension decoding. 26const png = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAAD" 27 28const no_files_home = "/nonexistent" 29 30const subscription = 31wire . Subscription ( 32"token" , 33"11111111-2222-4333-8444-555555555555" , 34"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" , 35"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee" , 36) 37 38pub fn claude_tool_schemas_flatten_only_top_level_combiners_test () -> Result ( 39Dynamic , 40List ( decode . DecodeError ), 41) { 42let assert Ok ( schema ) = 43json . parse ( 44"{ 45\"type\": \"object\", 46\"properties\": { 47\"urls\": { 48\"type\": \"array\", 49\"items\": { 50\"type\": \"string\" 51} 52}, 53\"ids\": { 54\"type\": \"array\", 55\"items\": { 56\"type\": \"string\" 57} 58} 59}, 60\"oneOf\": [ 61{ 62\"required\": [ 63\"urls\" 64] 65}, 66{ 67\"required\": [ 68\"ids\" 69] 70} 71], 72\"allOf\": [ 73{ 74\"required\": [ 75\"mode\" 76], 77\"properties\": { 78\"mode\": { 79\"oneOf\": [ 80{ 81\"type\": \"string\" 82}, 83{ 84\"type\": \"integer\" 85} 86] 87} 88} 89} 90] 91}" , 92decode . dynamic , 93) 94let request = 95types . Request ( 96"claude-opus-5-5" , 97None , 98[ types . User ( "hi" )], 99[ 100types . Tool ( 101"contents" , 102"Fetch URLs or IDs" , 103types . encode_value ( schema ), 104False , 105), 106], 107None , 108types . defaults , 109) 110let assert Ok ( openai_api . Exchange ( body : body , ..)) = 111wire . encode ( no_files_home , subscription , request ) 112let assert Ok ( value ) = json . parse ( sent ( body ), decode . dynamic ) 113let assert Ok ([ tool ]) = 114decode . run ( value , decode . at ([ "tools" ], decode . list ( decode . dynamic ))) 115let assert Ok ( "object" ) = 116decode . run ( tool , decode . at ([ "input_schema" , "type" ], decode . string )) 117let assert Ok ( props ) = 118decode . run ( 119tool , 120decode . at ( 121[ "input_schema" , "properties" ], 122decode . dict ( decode . string , decode . dynamic ), 123), 124) 125assert list . length ( dict . to_list ( props )) == 3 126let assert Ok ([ "mode" ]) = 127decode . run ( 128tool , 129decode . at ([ "input_schema" , "required" ], decode . list ( decode . string )), 130) 131let assert Ok ( hint ) = 132decode . run ( tool , decode . at ([ "input_schema" , "description" ], decode . string )) 133assert string . contains ( hint , "urls or ids" ) 134let assert Ok ([ _ , _ ]) = 135decode . run ( 136tool , 137decode . at ( 138[ "input_schema" , "properties" , "mode" , "oneOf" ], 139decode . list ( decode . dynamic ), 140), 141) 142let assert Error ( _ ) = 143decode . run ( tool , decode . at ([ "input_schema" , "oneOf" ], decode . dynamic )) 144let assert Error ( _ ) = 145decode . run ( tool , decode . at ([ "input_schema" , "allOf" ], decode . dynamic )) 146} 147 148pub fn claude_tool_schema_accepts_branch_only_root_union_test () -> Nil { 149let assert Ok ( schema ) = 150json . parse ( 151"{ 152\"oneOf\": [ 153{ 154\"type\": \"object\", 155\"properties\": { 156\"urls\": { 157\"type\": \"array\" 158} 159}, 160\"required\": [ 161\"urls\" 162] 163}, 164{ 165\"type\": \"object\", 166\"properties\": { 167\"ids\": { 168\"type\": \"array\" 169} 170}, 171\"required\": [ 172\"ids\" 173] 174} 175] 176}" , 177decode . dynamic , 178) 179let normalized = schemas . normalize ( types . encode_value ( schema )) 180let assert Ok ( value ) = json . parse ( json . to_string ( normalized ), decode . dynamic ) 181let assert Ok ( props ) = 182decode . run ( 183value , 184decode . at ([ "properties" ], decode . dict ( decode . string , decode . dynamic )), 185) 186assert list . length ( dict . to_list ( props )) == 2 187let assert Ok ( "object" ) = 188decode . run ( value , decode . at ([ "type" ], decode . string )) 189let assert Ok ( hint ) = 190decode . run ( value , decode . at ([ "description" ], decode . string )) 191assert string . contains ( hint , "urls or ids" ) 192} 193 194pub fn claude_schema_preserves_metadata_and_merge_precedence_in_requests_test () -> Nil { 195let assert Ok ( schema ) = 196json . parse ( 197"{ 198\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", 199\"$defs\": { 200\"choice\": { 201\"anyOf\": [ 202{ 203\"type\": \"string\" 204}, 205{ 206\"type\": \"null\" 207} 208] 209} 210}, 211\"x-metadata\": { 212\"values\": [ 213null, 214true, 2152.5, 216{ 217\"nested\": [ 218\"keep\" 219] 220} 221] 222}, 223\"additionalProperties\": false, 224\"description\": \"Choose inputs\", 225\"properties\": { 226\"shared\": { 227\"$ref\": \"#/$defs/choice\", 228\"x-custom\": [ 229false, 230null 231] 232} 233}, 234\"required\": [ 235\"root\", 236\"root\" 237], 238\"allOf\": [ 239{ 240\"properties\": { 241\"shared\": { 242\"type\": \"integer\" 243}, 244\"mode\": { 245\"oneOf\": [ 246{ 247\"const\": \"a\" 248}, 249{ 250\"const\": \"b\" 251} 252] 253} 254}, 255\"required\": [ 256\"mode\" 257] 258} 259], 260\"oneOf\": [ 261{ 262\"properties\": { 263\"left\": { 264\"type\": \"string\" 265}, 266\"collision\": { 267\"const\": \"first\" 268} 269}, 270\"required\": [ 271\"common\", 272\"left\" 273] 274}, 275{ 276\"properties\": { 277\"right\": { 278\"type\": \"string\" 279}, 280\"collision\": { 281\"const\": \"second\" 282} 283}, 284\"required\": [ 285\"right\", 286\"common\" 287] 288} 289], 290\"anyOf\": [ 291{ 292\"properties\": { 293\"extra\": { 294\"type\": \"boolean\" 295} 296}, 297\"required\": [ 298\"common\", 299\"extra\" 300] 301}, 302{ 303\"properties\": { 304\"other\": false 305}, 306\"required\": [ 307\"other\", 308\"common\" 309] 310} 311] 312}" , 313decode . dynamic , 314) 315let request = 316types . Request ( 317"claude-opus-5-5" , 318None , 319[ types . User ( "hi" )], 320[ types . Tool ( "inputs" , "Choose inputs" , types . encode_value ( schema ), False )], 321None , 322types . defaults , 323) 324let assert Ok ( openai_api . Exchange ( body : body , ..)) = 325wire . encode ( no_files_home , subscription , request ) 326let assert Ok ( value ) = json . parse ( sent ( body ), decode . dynamic ) 327let assert Ok ([ tool ]) = 328decode . run ( value , decode . at ([ "tools" ], decode . list ( decode . dynamic ))) 329let assert Ok ( actual ) = 330decode . run ( tool , decode . at ([ "input_schema" ], decode . dynamic )) 331let assert Ok ( expected ) = 332json . parse ( 333"{ 334\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", 335\"$defs\": { 336\"choice\": { 337\"anyOf\": [ 338{ 339\"type\": \"string\" 340}, 341{ 342\"type\": \"null\" 343} 344] 345} 346}, 347\"x-metadata\": { 348\"values\": [ 349null, 350true, 3512.5, 352{ 353\"nested\": [ 354\"keep\" 355] 356} 357] 358}, 359\"additionalProperties\": false, 360\"description\": \"Choose inputs; Exactly one of: common + left or right + common; At least one of: common + extra or other + common\", 361\"properties\": { 362\"shared\": { 363\"$ref\": \"#/$defs/choice\", 364\"x-custom\": [ 365false, 366null 367] 368}, 369\"mode\": { 370\"oneOf\": [ 371{ 372\"const\": \"a\" 373}, 374{ 375\"const\": \"b\" 376} 377] 378}, 379\"left\": { 380\"type\": \"string\" 381}, 382\"collision\": { 383\"const\": \"first\" 384}, 385\"right\": { 386\"type\": \"string\" 387}, 388\"extra\": { 389\"type\": \"boolean\" 390}, 391\"other\": false 392}, 393\"required\": [ 394\"common\", 395\"mode\", 396\"root\" 397], 398\"type\": \"object\" 399}" , 400decode . dynamic , 401) 402assert actual == expected 403} 404 405pub fn claude_stream_preserves_tool_calls_and_replay_test () -> Nil { 406let chunks = [ 407"{\"type\":\"message_start\",\"message\":{\"id\":\"msg_1\",\"usage\":{\"input_tokens\":12,\"output_tokens\":0,\"cache_read_input_tokens\":200,\"cache_creation_input_tokens\":14}}}" , 408"{\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}" , 409"{\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"hello\"}}" , 410"{\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"tool_1\",\"name\":\"Bash\",\"input\":{}}}" , 411"{\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"command\\\":\\\"pwd\\\"}\"}}" , 412"{\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"output_tokens\":7}}" , 413"{\"type\":\"message_stop\"}" , 414] 415let reducer = 416stream . reducer ( "claude-opus-5-5" , [ 417types . Tool ( "bash" , "run" , json . object ([]), False ), 418]) 419let assert Ok ( turn ) = feed_all ( reducer , chunks ) 420let assert [ call ] = turn . tool_calls 421assert call . name == "bash" 422assert call . arguments == "{\"command\":\"pwd\"}" 423assert turn . call_indices == [#( "tool_1" , 1 )] 424assert turn . finish == types . ToolCalls 425let assert Some ( usage ) = turn . usage 426assert usage . input_tokens == 226 427assert usage . output_tokens == 7 428assert usage . cached_input_tokens == Some ( 200 ) 429assert usage . cache_creation_tokens == Some ( 14 ) 430let request = 431types . Request ( 432"claude-opus-5-5" , 433None , 434[ 435types . User ( "start" ), 436types . Replay ( list_first ( turn . output )), 437types . ToolOutput ( "tool_1" , "ok" , []), 438], 439[], 440None , 441types . defaults , 442) 443let assert Ok ( openai_api . Exchange ( body : body , ..)) = 444wire . encode ( no_files_home , subscription , request ) 445let assert Ok ( value ) = json . parse ( sent ( body ), decode . dynamic ) 446let assert Ok ( names ) = 447decode . run ( 448value , 449decode . at ([ "messages" ], decode . list ( decode . at ([ "role" ], decode . string ))), 450) 451assert names == [ "user" , "assistant" , "user" ] 452let encoded = sent ( body ) 453assert string . contains ( encoded , "tool_result" ) 454assert string . contains ( encoded , "tool_use" ) 455assert string . contains ( encoded , "hello" ) 456} 457 458pub fn claude_refusal_details_and_text_reach_failure_message_test () -> Nil { 459let assert Ok ( turn ) = 460feed_all ( stream . reducer ( "claude-opus-5-5" , []), [ 461"{\"type\":\"message_start\",\"message\":{\"id\":\"msg_refusal\"}}" , 462"{\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}" , 463"{\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"I can’t help with that request.\"}}" , 464"{\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"refusal\",\"stop_details\":{\"type\":\"refusal\",\"category\":\"policy\",\"explanation\":\"private credentials\"}}}" , 465"{\"type\":\"message_stop\"}" , 466]) 467let failure = loop . stopped_message ( turn . finish , turn . output ) 468assert string . contains ( failure , "refusal (policy): private credentials" ) 469assert string . contains ( failure , "I can’t help with that request." ) 470} 471 472pub fn claude_stream_shows_thinking_and_replays_it_signed_test () -> Nil { 473let chunks = [ 474"{\"type\":\"message_start\",\"message\":{\"id\":\"msg_1\",\"usage\":{\"input_tokens\":3,\"output_tokens\":0}}}" , 475"{\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\"}}" , 476"{\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"weighing \"}}" , 477"{\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"options\"}}" , 478"{\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"sig_1\"}}" , 479"{\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}" , 480"{\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"done\"}}" , 481"{\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"}}" , 482"{\"type\":\"message_stop\"}" , 483] 484let #( deltas , turn ) = 485feed_thinking ( stream . reducer ( "claude-opus-5-5" , []), chunks , []) 486assert deltas == [ "weighing " , "options" ] 487let item = list_first ( turn . output ) 488assert events . thinking_text ( item ) == "weighing options" 489let request = 490types . Request ( 491"claude-opus-5-5" , 492None , 493[ types . User ( "start" ), types . Replay ( item ), types . User ( "next" )], 494[], 495None , 496types . defaults , 497) 498let assert Ok ( openai_api . Exchange ( body : body , ..)) = 499wire . encode ( no_files_home , subscription , request ) 500let assert Ok ( value ) = json . parse ( sent ( body ), decode . dynamic ) 501let block = fn ( name ) { 502decode . at ( 503[ "messages" ], 504decode . list ( decode . at ( 505[ "content" ], 506decode . list ( decode . optional_field ( 507name , 508"" , 509decode . string , 510decode . success , 511)), 512)), 513) 514} 515let assert Ok ([ _ , [ thinking , _ ], _ ]) = decode . run ( value , block ( "thinking" )) 516assert thinking == "weighing options" 517let assert Ok ([ _ , [ signature , _ ], _ ]) = decode . run ( value , block ( "signature" )) 518assert signature == "sig_1" 519 520let entry = 521transcript . Entry ( types . Replay ( item ), None , Some ( "claude" ), None , None ) 522let assert Ok ([ types . Assistant ( "done" ), types . Assistant ( summary )]) = 523projection . for_model ([ entry ], "codex" , types . Responses ) 524assert summary == "[Reasoning summary]\nweighing options" 525let assert Ok ( codex_body ) = 526request . encode ( 527types . Responses , 528types . Request ( 529"gpt-5" , 530None , 531[ types . Assistant ( summary )], 532[], 533None , 534types . defaults , 535), 536) 537assert string . contains ( sent ( codex_body ), "weighing options" ) 538 539let changed = 540types . Request ( 541"claude-sonnet-4-6" , 542None , 543[ types . Replay ( item )], 544[], 545None , 546types . defaults , 547) 548let assert Ok ( openai_api . Exchange ( body : switched , ..)) = 549wire . encode ( no_files_home , subscription , changed ) 550assert string . contains ( sent ( switched ), "[Reasoning summary]" ) 551assert ! string . contains ( sent ( switched ), "sig_1" ) 552} 553 554pub fn codex_reasoning_summary_survives_transfer_to_claude_test () -> Nil { 555let assert Ok ( item ) = 556json . parse ( 557"{\"type\":\"reasoning\",\"summary\":[{\"type\":\"summary_text\",\"text\":\"first\"},{\"type\":\"summary_text\",\"text\":\"second\"}],\"encrypted_content\":\"sealed-reasoning\"}" , 558types . replay_decoder ( types . Responses ), 559) 560let entry = 561transcript . Entry ( types . Replay ( item ), None , Some ( "codex" ), None , None ) 562let assert Ok ([ types . Assistant ( summary )]) = 563projection . for_model ([ entry ], "other-codex" , types . Responses ) 564assert summary == "[Reasoning summary]\nfirst\n\nsecond" 565let assert Ok ([ types . Replay ( projected )]) = 566projection . for_model ([ entry ], "claude" , types . ChatCompletions ) 567let request = 568types . Request ( 569"claude-opus-5-5" , 570None , 571[ types . Replay ( projected )], 572[], 573None , 574types . defaults , 575) 576let assert Ok ( openai_api . Exchange ( body : body , ..)) = 577wire . encode ( no_files_home , subscription , request ) 578let sent = sent ( body ) 579assert string . contains ( sent , "[Reasoning summary]" ) 580assert string . contains ( sent , "first" ) 581assert string . contains ( sent , "second" ) 582assert ! string . contains ( sent , "sealed-reasoning" ) 583} 584 585/// Feeds every chunk and keeps the thinking deltas the stream emitted. 586fn feed_thinking ( 587reducer : reducer . Reducer , 588chunks : List ( String ), 589deltas : List ( String ), 590) -> #( List ( String ), types . Turn ) { 591let assert [ chunk , .. rest ] = chunks 592let assert Ok (#( next , emitted , turn )) = reducer . feed ( chunk ) 593let deltas = 594list . fold ( emitted , deltas , fn ( deltas , event ) { 595case event { 596types . ThinkingDelta ( text ) -> [ text , .. deltas ] 597_ -> deltas 598} 599}) 600case turn { 601Some ( turn ) -> #( list . reverse ( deltas ), turn ) 602None -> feed_thinking ( next , rest , deltas ) 603} 604} 605 606pub fn claude_billing_hash_agrees_across_chunk_boundaries_test () -> Nil { 607let body = 608string . repeat ( 609"{\"messages\":[{\"role\":\"user\",\"content\":\"hello cch=00000\"}]," , 61037 , 611) 612assert billing_hash ( body ) == billing_hash_streamed ([ body ]) 613assert billing_hash ( body ) == billing_hash_streamed ( split_every ( body , 1 )) 614assert billing_hash ( body ) == billing_hash_streamed ( split_every ( body , 7 )) 615assert billing_hash ( body ) == billing_hash_streamed ( split_every ( body , 13 )) 616assert billing_hash ( body ) == billing_hash_streamed ( split_every ( body , 31 )) 617assert billing_hash ( body ) == billing_hash_streamed ( split_every ( body , 32 )) 618assert billing_hash ( body ) == billing_hash_streamed ( split_every ( body , 33 )) 619assert billing_hash ( body ) 620== billing_hash_streamed ([ 621string . slice ( body , 0 , 31 ), 622string . drop_start ( body , 31 ), 623]) 624assert billing_hash ( body ) 625== billing_hash_streamed ([ 626string . slice ( body , 0 , 32 ), 627string . drop_start ( body , 32 ), 628]) 629} 630 631fn split_every ( text : String , size : Int ) -> List ( String ) { 632case string . byte_size ( text ) <= size { 633True -> [ text ] 634False -> [ 635string . slice ( text , 0 , size ), 636.. split_every ( string . drop_start ( text , size ), size ) 637] 638} 639} 640 641type Fixture 642 643type ServerMode { 644Stream 645} 646 647@external ( erlang , "albedo_openai_transport_test_server" , "start" ) 648fn start ( mode : ServerMode ) -> Fixture 649 650@external ( erlang , "albedo_openai_transport_test_server" , "url" ) 651fn url ( fixture : Fixture ) -> String 652 653@external ( erlang , "albedo_openai_transport_test_server" , "await_body" ) 654fn await_body ( fixture : Fixture ) -> BitArray 655 656@external ( erlang , "albedo_openai_transport_test_server" , "stop" ) 657fn stop ( fixture : Fixture ) -> Nil 658 659pub fn claude_attested_body_streams_through_the_transport_test () -> Nil { 660let fixture = start ( Stream ) 661let image = 662stored_image ( 663"image/png" , 664string . repeat ( "ab" , 32 ), 665string . byte_size ( png ), 666fn () { Ok ( png ) }, 6672 , 6683 , 66924 , 670) 671let request = 672types . Request ( 673"claude-opus-5-5" , 674None , 675[ types . UserImage ( "what is this" , [ image ])], 676[], 677None , 678types . defaults , 679) 680let assert Ok ( openai_api . Exchange ( body : body , ..)) = 681wire . encode ( no_files_home , subscription , request ) 682let assert Ok ( connection ) = transport . open ( url ( fixture ), [], body , 2000 ) 683let received = await_body ( fixture ) 684transport . close ( connection ) 685stop ( fixture ) 686let assert Ok ( on_the_wire ) = bit_array . to_string ( received ) 687let assert Ok ( materialized ) = materialize ( body ) 688assert on_the_wire == materialized 689assert string . contains ( on_the_wire , png ) 690let assert Ok ( value ) = json . parse ( on_the_wire , decode . dynamic ) 691let assert Ok ([ billing , _ ]) = 692decode . run ( 693value , 694decode . at ([ "system" ], decode . list ( decode . at ([ "text" ], decode . string ))), 695) 696let assert [ _ , hash_with_end ] = string . split ( billing , "cch=" ) 697let assert [ hash , "" ] = string . split ( hash_with_end , ";" ) 698let unsigned = string . replace ( on_the_wire , "cch=" <> hash , "cch=00000" ) 699assert hash == billing_hash ( unsigned ) 700} 701 702pub fn claude_first_user_surrogates_do_not_crash_the_billing_sample_test () -> Nil { 703let request = 704types . Request ( 705"claude-opus-5-5" , 706None , 707[ types . User ( "abcd🚀 let's look at 🌟 and 🎉 together" )], 708[], 709None , 710types . defaults , 711) 712let assert Ok ( exchange ) = wire . encode ( no_files_home , subscription , request ) 713let text = sent ( exchange . body ) 714let assert Ok ( value ) = json . parse ( text , decode . dynamic ) 715let assert Ok ([ billing , _ ]) = 716decode . run ( 717value , 718decode . at ([ "system" ], decode . list ( decode . at ([ "text" ], decode . string ))), 719) 720let assert [ _ , hash_with_end ] = string . split ( billing , "cch=" ) 721let assert [ hash , "" ] = string . split ( hash_with_end , ";" ) 722let unsigned = string . replace ( text , "cch=" <> hash , "cch=00000" ) 723assert hash == billing_hash ( unsigned ) 724} 725 726pub fn claude_token_response_becomes_the_stored_credential_test () -> Nil { 727let assert Ok ( value ) = 728claude_token ( 729"{\"access_token\":\"tok\",\"refresh_token\":\"r\",\"expires_in\":86400}" , 730) 731let assert Ok ( "oauth" ) = decode . run ( value , decode . at ([ "type" ], decode . string )) 732let assert Ok ( "tok" ) = decode . run ( value , decode . at ([ "access" ], decode . string )) 733let assert Ok ( expires ) = decode . run ( value , decode . at ([ "expires" ], decode . int )) 734assert expires > 0 735let assert Ok ( account ) = 736decode . run ( value , decode . at ([ "accountId" ], decode . string )) 737assert string . byte_size ( account ) == 16 738} 739 740pub fn claude_billing_hash_matches_reference_vectors_test () -> Nil { 741assert billing_hash ( "cch=00000" ) == "a47f7" 742assert billing_hash ( "{\"messages\":[],\"cch=00000\",\"x\":1}" ) == "3073d" 743assert billing_hash ( 744"x-anthropic-billing-header: cc_version=2.1.158; cc_entrypoint=cli; cch=00000;" , 745) 746== "f2b0b" 747assert billing_hash ( "" ) == "b46c5" 748assert billing_hash ( string . repeat ( "a" , 31 )) == "ef013" 749assert billing_hash ( string . repeat ( "a" , 32 )) == "86567" 750assert billing_hash ( string . repeat ( "a" , 33 )) == "5bea8" 751assert billing_hash ( string . repeat ( "b" , 63 )) == "9f9d8" 752assert billing_hash ( string . repeat ( "c" , 64 )) == "2742b" 753assert billing_hash ( string . repeat ( "d" , 65 )) == "92299" 754assert billing_hash ( string . repeat ( "0123456789" , 100 )) == "827e8" 755} 756 757@external ( erlang , "albedo_claude_auth" , "token" ) 758fn claude_token ( response : String ) -> Result ( Dynamic , String ) 759 760/// The ledger records `wire.cache_marks` as what a Claude request asked to 761/// cache; it must name exactly the cache_control markers `encode` sends. 762pub fn claude_cache_marks_match_the_encoded_markers_test () -> Nil { 763let requests = [ 764types . Request ( 765"claude-opus-5-5" , 766Some ( "be brief" ), 767[ types . User ( "a" ), types . Assistant ( "b" ), types . User ( "c" )], 768[ 769types . Tool ( "read" , "read" , json . object ([]), False ), 770types . Tool ( "bash" , "run" , json . object ([]), False ), 771], 772None , 773types . defaults , 774), 775types . Request ( 776"claude-opus-5-5" , 777None , 778[ types . User ( "hi" )], 779[], 780None , 781types . defaults , 782), 783] 784use request <- list . each ( requests ) 785use auth <- list . each ([ subscription , wire . ApiKey ( "sk-ant-test" )]) 786{ 787let assert Ok ( openai_api . Exchange ( body : body , ..)) = 788wire . encode ( no_files_home , auth , request ) 789let assert Ok ( value ) = json . parse ( sent ( body ), decode . dynamic ) 790// A request without tools sends no tools field at all. 791let markers = fn ( field ) { 792let assert Ok ( blocks ) = 793decode . run ( 794value , 795decode . optional_field ( 796field , 797[], 798decode . list ( marker_decoder ()), 799decode . success , 800), 801) 802option . values ( blocks ) 803} 804let assert Ok ( messages ) = 805decode . run ( 806value , 807decode . at ( 808[ "messages" ], 809decode . list ( decode . at ([ "content" ], decode . list ( marker_decoder ()))), 810), 811) 812let last_message = list . length ( messages ) - 1 813let input = 814messages 815|> list . index_map ( fn ( blocks , at ) { 816let last_block = list . length ( blocks ) - 1 817list . index_map ( blocks , fn ( ttl , block ) { 818case ttl , at == last_message && block == last_block { 819Some ( ttl ), True -> [ 820#( types . InputSpan ( list . length ( request . input ) - 1 ), ttl ), 821] 822// A marker anywhere but the final block matches no declared mark. 823Some ( ttl ), False -> [#( types . InputSpan ( -1 ), ttl )] 824None , _ -> [] 825} 826}) 827}) 828|> list . flatten 829|> list . flatten 830let encoded = 831list . flatten ([ 832list . map ( markers ( "tools" ), fn ( ttl ) { #( types . ToolsSpan , ttl ) }), 833list . map ( markers ( "system" ), fn ( ttl ) { #( types . SystemSpan , ttl ) }), 834input , 835]) 836let declared = 837wire . cache_marks ( request ) 838|> list . map ( fn ( mark ) { #( mark . through , mark . ttl_seconds ) }) 839assert declared == encoded 840} 841} 842 843/// A block's cache_control TTL in seconds, when it carries one. 844fn marker_decoder () -> decode . Decoder ( option . Option ( Int )) { 845let ttl = { 846use ttl <- decode . optional_field ( "ttl" , "5m" , decode . string ) 847decode . success ( case ttl { 848"1h" -> 3600 849_ -> 300 850}) 851} 852decode . optional_field ( 853"cache_control" , 854None , 855decode . optional ( ttl ), 856decode . success , 857) 858} 859 860/// A Console key bills per token, so it drops the subscription's billing 861/// block, metadata and OAuth shape, but premium models answer a headerless 429 862/// unless the Claude Code identity still leads the system prompt. 863pub fn claude_api_key_requests_keep_only_the_identity_test () -> Result ( 864List ( String ), 865List ( decode . DecodeError ), 866) { 867let request = 868types . Request ( 869"claude-opus-5-5" , 870None , 871[ types . User ( "hi" )], 872[], 873None , 874types . defaults , 875) 876let assert Ok ( exchange ) = 877wire . encode ( no_files_home , wire . ApiKey ( "sk-ant-test" ), request ) 878let assert Ok ( "sk-ant-test" ) = list . key_find ( exchange . headers , "x-api-key" ) 879let assert Error ( Nil ) = list . key_find ( exchange . headers , "authorization" ) 880let assert Error ( Nil ) = list . key_find ( exchange . headers , "x-app" ) 881let assert Ok ( betas ) = list . key_find ( exchange . headers , "anthropic-beta" ) 882assert ! string . contains ( betas , "claude-code" ) 883assert ! string . contains ( betas , "oauth" ) 884let text = sent ( exchange . body ) 885assert ! string . contains ( text , "cch=" ) 886let assert Ok ( value ) = json . parse ( text , decode . dynamic ) 887let absent = fn ( field ) { 888decode . run ( 889value , 890decode . optional_field ( field , True , decode . success ( False ), decode . success ), 891) 892} 893let assert Ok ( True ) = absent ( "metadata" ) 894let assert Ok ([ "You are Claude Code, Anthropic's official CLI for Claude." ]) = 895decode . run ( 896value , 897decode . at ([ "system" ], decode . list ( decode . at ([ "text" ], decode . string ))), 898) 899} 900 901@external ( erlang , "albedo_claude_billing" , "hash" ) 902fn billing_hash ( body : String ) -> String 903 904@external ( erlang , "albedo_claude_billing" , "hash_streamed" ) 905fn billing_hash_streamed ( chunks : List ( String )) -> String 906 907@external ( erlang , "albedo_openai_transport" , "materialize" ) 908fn materialize ( body : StringTree ) -> Result ( String , String ) 909 910fn sent ( body : StringTree ) -> String { 911let assert Ok ( text ) = materialize ( body ) 912text 913} 914 915@external ( erlang , "albedo_claude_test_support" , "stored_image" ) 916fn stored_image ( 917mime : String , 918hash : String , 919size : Int , 920read : fn () -> Result ( String , Nil ), 921width : Int , 922height : Int , 923bytes : Int , 924) -> types . Image 925 926fn feed_all ( 927reducer : reducer . Reducer , 928chunks : List ( String ), 929) -> Result ( types . Turn , types . Error ) { 930case chunks { 931[] -> Error ( types . UnexpectedEnd ) 932[ chunk , .. rest ] -> { 933use #( next , _ , turn ) <- result . try ( reducer . feed ( chunk )) 934case turn { 935Some ( turn ) -> Ok ( turn ) 936None -> feed_all ( next , rest ) 937} 938} 939} 940} 941 942fn list_first ( items : List ( types . ReplayItem )) -> types . ReplayItem { 943let assert [ first , ..] = items 944first 945} 946 947@external ( erlang , "albedo_skills_test_support" , "fixture" ) 948fn fixture () -> #( String , String , String ) 949 950@external ( erlang , "albedo_skills_test_support" , "write" ) 951fn write ( base : String , path : String , contents : String ) -> String 952 953@external ( erlang , "albedo_skills_test_support" , "cleanup" ) 954fn cleanup ( root : String ) -> Nil 955 956@external ( erlang , "albedo_claude_auth" , "accounts" ) 957fn claude_accounts ( home : String ) -> List ( Dynamic ) 958 959// A legacy anthropic account stored without a refresh token crashed quota 960// enumeration with `bad key: <<"refresh">>` in refresh_current/3, taking out 961// every Claude account's readings; E2E only ever sees that crash as log noise 962// from a background spawn, so the direct call is the only clean reach. Such an 963// account is never refreshed and stays as stored, like a refresh that failed, 964// beside its healthy sibling. 965pub fn claude_accounts_without_a_refresh_token_stay_enumerable_test () -> Nil { 966let #( root , _ , home ) = fixture () 967let _ = 968write ( 969home , 970"creds.json" , 971"{\"accounts\":{\"anthropic\":[{\"type\":\"oauth\",\"access\":\"legacy-access\"}," 972<> "{\"type\":\"oauth\",\"access\":\"fresh-access\",\"refresh\":\"r\"," 973<> "\"expires\":4102444800000}]}}" , 974) 975let access = decode . at ([ "access" ], decode . string ) 976let assert [ Ok ( "legacy-access" ), Ok ( "fresh-access" )] = 977list . map ( claude_accounts ( home ), decode . run ( _, access )) 978cleanup ( root ) 979} 980 981// Claude's fixed authenticated endpoint cannot receive the E2E fixture payloads. 982pub fn malformed_stream_diagnostics_preserve_types_without_payloads_test () -> Nil { 983let reducer = stream . reducer ( "claude-opus-5-5" , []) 984let assert Error ( types . InvalidEvent ( message )) = 985reducer . feed ( 986"{\"type\":\"content_block_delta\",\"index\":\"secret-provider-value\",\"delta\":{\"type\":\"text_delta\"}}" , 987) 988assert string . contains ( message , "$.index" ) 989assert string . contains ( message , "expected Int, found String" ) 990assert ! string . contains ( message , "secret-provider-value" ) 991let assert Error ( types . InvalidEvent ( message )) = 992reducer . feed ( "{\"type\":\"secret-provider-value" ) 993assert string . contains ( message , "unexpected end of JSON" ) 994assert ! string . contains ( message , "secret-provider-value" ) 995}