okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
b3ecc04
main
1// OpenAI streaming wire-format errors and partial tool calls must never become executable output. 2import albedo/openai_api/sse 3import albedo/openai_api/stream 4import albedo/openai_api/types 5import gleam/dynamic/decode 6import gleam/json 7import gleam/list 8import gleam/option .{ type Option , None , Some } 9 10pub fn responses_incomplete_never_exposes_executable_calls_test () -> Result ( 11#( stream . State , List ( types . Event ), Option ( types . Turn )), 12types . Error , 13) { 14let data = 15"{\"type\":\"response.incomplete\",\"response\":{\"output\":[{\"type\":\"function_call\",\"call_id\":\"danger\",\"name\":\"delete\",\"arguments\":\"{}\"}],\"incomplete_details\":{\"reason\":\"max_output_tokens\"}}}" 16let assert Ok (#( 17_ , 18[], 19Some ( types . Turn ( None , [ _ ], [], None , types . LengthLimit , None , [])), 20)) = send ( stream . new ( types . Responses ), "response.incomplete" , data ) 21} 22 23pub fn responses_provider_failures_and_malformed_fields_are_typed_test () -> Nil { 24let failed = 25"{\"type\":\"response.failed\",\"response\":{\"error\":{\"message\":\"capacity\"}}}" 26assert send ( stream . new ( types . Responses ), "response.failed" , failed ) 27== Error ( types . ProviderError ( "capacity" )) 28let malformed = "{\"type\":\"response.created\",\"response\":{\"id\":42}}" 29let assert Error ( types . InvalidEvent ( _ )) = 30send ( stream . new ( types . Responses ), "response.created" , malformed ) 31let unsupported = 32"{\"type\":\"response.custom_tool_call_input.delta\",\"delta\":\"rm\"}" 33assert send ( 34stream . new ( types . Responses ), 35"response.custom_tool_call_input.delta" , 36unsupported , 37) 38== Error ( types . Unsupported ( 39"unsupported Responses semantic event: response.custom_tool_call_input.delta" , 40)) 41} 42 43pub fn responses_argument_deltas_name_the_tool_their_item_opened_with_test () -> Result ( 44#( stream . State , List ( types . Event ), Option ( types . Turn )), 45types . Error , 46) { 47let added = 48"{\"type\":\"response.output_item.added\",\"output_index\":1,\"item\":{\"type\":\"function_call\",\"call_id\":\"c\",\"name\":\"web_search\",\"arguments\":\"\"}}" 49let assert Ok (#( state , [], None )) = 50send ( stream . new ( types . Responses ), "response.output_item.added" , added ) 51let delta = 52"{\"type\":\"response.function_call_arguments.delta\",\"output_index\":1,\"delta\":\"{\\\"q\"}" 53let assert Ok (#( _ , [ types . ArgumentsDelta ( 1 , "web_search" , "{\"q" )], None )) = 54send ( state , "response.function_call_arguments.delta" , delta ) 55} 56 57pub fn chat_accumulates_interleaved_tools_usage_and_native_replay_test () -> Nil { 58let state = stream . new ( types . ChatCompletions ) 59let first = 60"{\"id\":\"chat_1\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"hel\",\"reasoning_content\":\"think \"},\"finish_reason\":null}]}" 61let assert Ok (#( 62state , 63[ 64types . Started ( "chat_1" ), 65types . ThinkingDelta ( "think " ), 66types . TextDelta ( 0 , 0 , "hel" ), 67], 68None , 69)) = send ( state , "" , first ) 70let one = 71"{\"id\":\"chat_1\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":1,\"id\":\"call_b\",\"type\":\"function\",\"function\":{\"name\":\"beta\",\"arguments\":\"{\\\"b\\\":\"}},{\"index\":0,\"id\":\"call_a\",\"type\":\"function\",\"function\":{\"name\":\"alpha\",\"arguments\":\"{\\\"a\\\":\"}}]},\"finish_reason\":null}]}" 72let assert Ok (#( 73state , 74[ 75types . ArgumentsDelta ( 1 , "beta" , "{\"b\":" ), 76types . ArgumentsDelta ( 0 , "alpha" , "{\"a\":" ), 77], 78None , 79)) = send ( state , "" , one ) 80let two = 81"{\"id\":\"chat_1\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"lo\",\"refusal\":\"no\",\"reasoning_content\":\"carefully\",\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"1}\"}},{\"index\":1,\"function\":{\"arguments\":\"2}\"}}]},\"finish_reason\":\"tool_calls\"}]}" 82let assert Ok (#( state , events , None )) = send ( state , "" , two ) 83assert events 84== [ 85types . ThinkingDelta ( "carefully" ), 86types . TextDelta ( 0 , 0 , "lo" ), 87types . ArgumentsDelta ( 0 , "alpha" , "1}" ), 88types . ArgumentsDelta ( 1 , "beta" , "2}" ), 89] 90let usage = 91"{\"id\":\"chat_1\",\"choices\":[],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":8,\"total_tokens\":13,\"prompt_tokens_details\":{\"cached_tokens\":0}}}" 92let assert Ok (#( state , [], None )) = send ( state , "" , usage ) 93let assert Ok (#( 94_ , 95[], 96Some ( types . Turn ( 97Some ( "chat_1" ), 98[ message ], 99[ 100types . ToolCall ( "call_a" , "alpha" , "{\"a\":1}" ), 101types . ToolCall ( "call_b" , "beta" , "{\"b\":2}" ), 102], 103Some ( types . Usage ( 5 , 8 , Some ( 0 ), None , None , None , None )), 104types . ToolCalls , 105None , 106[#( "call_a" , 0 ), #( "call_b" , 1 )], 107)), 108)) = send ( state , "" , "[DONE]" ) 109let decoder = { 110use content <- decode . field ( "content" , decode . string ) 111use refusal <- decode . field ( "refusal" , decode . string ) 112use reasoning <- decode . field ( "reasoning_content" , decode . string ) 113use tool_ids <- decode . field ( 114"tool_calls" , 115decode . list ({ 116use id <- decode . field ( "id" , decode . string ) 117decode . success ( id ) 118}), 119) 120decode . success (#( content , refusal , reasoning , tool_ids )) 121} 122assert types . inspect_item ( message , decoder ) 123== Ok (#( "hello" , "no" , "think carefully" , [ "call_a" , "call_b" ])) 124} 125 126pub fn cache_usage_details_tolerate_null_and_reject_malformed_values_test () -> Result ( 127#( stream . State , List ( types . Event ), Option ( types . Turn )), 128types . Error , 129) { 130let responses_null = 131"{\"type\":\"response.completed\",\"response\":{\"output\":[],\"usage\":{\"input_tokens\":3,\"output_tokens\":2,\"input_tokens_details\":null}}}" 132let assert Ok (#( 133_ , 134[], 135Some ( types . Turn ( 136_ , 137[], 138[], 139Some ( types . Usage ( 3 , 2 , None , None , None , None , None )), 140_ , 141None , 142[], 143)), 144)) = send ( stream . new ( types . Responses ), "" , responses_null ) 145 146let chat_null = 147"{\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2,\"prompt_tokens_details\":null}}" 148let assert Ok (#( state , _ , None )) = 149send ( stream . new ( types . ChatCompletions ), "" , chat_null ) 150let assert Ok (#( 151_ , 152[], 153Some ( types . Turn ( 154_ , 155_ , 156[], 157Some ( types . Usage ( 3 , 2 , None , None , None , None , None )), 158_ , 159None , 160[], 161)), 162)) = send ( state , "" , "[DONE]" ) 163 164let responses_malformed = 165"{\"type\":\"response.completed\",\"response\":{\"output\":[],\"usage\":{\"input_tokens\":3,\"output_tokens\":2,\"input_tokens_details\":{\"cached_tokens\":\"3\"}}}}" 166let assert Error ( types . InvalidEvent ( _ )) = 167send ( stream . new ( types . Responses ), "" , responses_malformed ) 168 169let chat_malformed = 170"{\"choices\":[],\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2,\"prompt_tokens_details\":{\"cached_tokens\":\"3\"}}}" 171let assert Error ( types . InvalidEvent ( _ )) = 172send ( stream . new ( types . ChatCompletions ), "" , chat_malformed ) 173} 174 175pub fn responses_separates_reasoning_summary_parts_test () -> Result ( 176#( stream . State , List ( types . Event ), Option ( types . Turn )), 177types . Error , 178) { 179let part = fn ( index ) { 180"{\"type\":\"response.reasoning_summary_part.added\",\"output_index\":0,\"summary_index\":" 181<> index 182<> ",\"part\":{\"type\":\"summary_text\",\"text\":\"\"}}" 183} 184let state = stream . new ( types . Responses ) 185let assert Ok (#( state , [], None )) = send ( state , "" , part ( "0" )) 186let assert Ok (#( _state , [ types . ThinkingDelta ( "\n\n" )], None )) = 187send ( state , "" , part ( "1" )) 188} 189 190pub fn chat_rejects_multiple_choices_and_nonempty_unknown_semantics_test () -> Result ( 191#( stream . State , List ( types . Event ), Option ( types . Turn )), 192types . Error , 193) { 194let multiple = 195"{\"choices\":[{\"index\":0,\"delta\":{}},{\"index\":1,\"delta\":{}}]}" 196assert send ( stream . new ( types . ChatCompletions ), "" , multiple ) 197== Error ( types . Unsupported ( "multiple chat completion choices" )) 198let audio = 199"{\"choices\":[{\"index\":0,\"delta\":{\"audio\":{\"id\":\"a\"}}}]}" 200let assert Error ( types . InvalidEvent ( _ )) = 201send ( stream . new ( types . ChatCompletions ), "" , audio ) 202} 203 204pub fn chat_ignores_only_empty_unknown_fields_test () -> Nil { 205list . each ([ "null" , "\"\"" , "[]" , "{}" ], fn ( value ) { 206let data = 207"{\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hello\",\"audio\":" 208<> value 209<> "}}]}" 210let assert Ok (#( _ , [ types . TextDelta ( 0 , 0 , "hello" )], None )) = 211send ( stream . new ( types . ChatCompletions ), "" , data ) 212}) 213list . each ([ "\"x\"" , "[0]" , "{\"id\":null}" , "0" , "false" , "true" ], fn ( value ) { 214let data = 215"{\"choices\":[{\"index\":0,\"delta\":{\"audio\":" <> value <> "}}]}" 216let assert Error ( types . InvalidEvent ( _ )) = 217send ( stream . new ( types . ChatCompletions ), "" , data ) 218}) 219} 220 221pub fn chat_rejects_non_object_deltas_test () -> Nil { 222list . each ([ "null" , "[]" , "42" , "\"x\"" , "false" ], fn ( value ) { 223let data = "{\"choices\":[{\"index\":0,\"delta\":" <> value <> "}]}" 224let assert Error ( types . InvalidEvent ( _ )) = 225send ( stream . new ( types . ChatCompletions ), "" , data ) 226}) 227} 228 229pub fn chat_provider_errors_take_precedence_only_with_string_messages_test () -> Nil { 230list . each ([ "" , "capacity" ], fn ( message ) { 231let data = 232json . object ([ 233#( "error" , json . object ([#( "message" , json . string ( message ))])), 234#( 235"choices" , 236json . array ([ "hello" ], fn ( content ) { 237json . object ([ 238#( "index" , json . int ( 0 )), 239#( "delta" , json . object ([#( "content" , json . string ( content ))])), 240]) 241}), 242), 243]) 244|> json . to_string 245assert send ( stream . new ( types . ChatCompletions ), "" , data ) 246== Error ( types . ProviderError ( message )) 247}) 248list . each ( 249[ 250"null" , "42" , "\"x\"" , "{}" , "{\"message\":null}" , "{\"message\":42}" , 251"{\"message\":false}" , "{\"message\":[]}" , 252], 253fn ( value ) { 254let data = 255"{\"error\":" 256<> value 257<> ",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hello\"}}]}" 258let assert Ok (#( _ , [ types . TextDelta ( 0 , 0 , "hello" )], None )) = 259send ( stream . new ( types . ChatCompletions ), "" , data ) 260}, 261) 262let assert Error ( types . InvalidEvent ( _ )) = 263send ( stream . new ( types . ChatCompletions ), "" , "{\"error\":{\"message\":42}}" ) 264Nil 265} 266 267pub fn chat_requires_finish_and_suppresses_partial_tools_on_limits_test () -> Nil { 268assert send ( stream . new ( types . ChatCompletions ), "" , "[DONE]" ) 269== Error ( types . InvalidEvent ( "[DONE] before chat finish reason" )) 270let partial = 271"{\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_partial\",\"type\":\"function\",\"function\":{\"name\":\"unsafe\",\"arguments\":\"{\"}}]},\"finish_reason\":\"length\"}]}" 272let assert Ok (#( state , _ , None )) = 273send ( stream . new ( types . ChatCompletions ), "" , partial ) 274let assert Ok (#( 275_ , 276[], 277Some ( types . Turn ( None , [ message ], [], None , types . LengthLimit , None , [])), 278)) = send ( state , "" , "[DONE]" ) 279let decoder = { 280use calls <- decode . optional_field ( 281"tool_calls" , 282None , 283decode . optional ( decode . list ( decode . dynamic )), 284) 285decode . success ( calls ) 286} 287assert types . inspect_item ( message , decoder ) == Ok ( None ) 288} 289 290pub fn malformed_json_and_content_filter_finish_are_typed_test () -> Result ( 291#( stream . State , List ( types . Event ), Option ( types . Turn )), 292types . Error , 293) { 294let assert Error ( types . InvalidEvent ( _ )) = 295send ( stream . new ( types . ChatCompletions ), "" , "{" ) 296let assert Error ( types . InvalidEvent ( _ )) = 297send ( stream . new ( types . Responses ), "" , "[]" ) 298 299let filtered = 300"{\"choices\":[{\"index\":0,\"delta\":{\"content\":\"safe prefix\"},\"finish_reason\":\"content_filter\"}]}" 301let assert Ok (#( state , _ , None )) = 302send ( stream . new ( types . ChatCompletions ), "" , filtered ) 303let assert Ok (#( 304_ , 305[], 306Some ( types . Turn ( _ , _ , [], _ , types . ContentFiltered , None , [])), 307)) = send ( state , "" , "[DONE]" ) 308} 309 310pub fn chat_assembles_name_fragments_and_encodes_tool_only_content_as_null_test () -> Result ( 311#( stream . State , List ( types . Event ), Option ( types . Turn )), 312types . Error , 313) { 314let first = 315"{\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call1\",\"function\":{\"name\":\"read_\",\"arguments\":\"{\"}}]}}]}" 316let second = 317"{\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"name\":\"file\",\"arguments\":\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}" 318let assert Ok (#( state , _ , None )) = 319send ( stream . new ( types . ChatCompletions ), "" , first ) 320let assert Ok (#( state , _ , None )) = send ( state , "" , second ) 321let assert Ok (#( state , _ , Some ( turn ))) = send ( state , "" , "[DONE]" ) 322assert turn . tool_calls == [ types . ToolCall ( "call1" , "read_file" , "{}" )] 323let assert [ item ] = turn . output 324let encoded = types . replay_json ( item ) |> json . to_string 325assert json . parse ( 326encoded , 327decode . at ([ "content" ], decode . optional ( decode . string )), 328) 329== Ok ( None ) 330let assert Error ( types . InvalidEvent ( _ )) = send ( state , "" , "[DONE]" ) 331} 332 333pub fn chat_rejects_data_after_finish_reason_test () -> Result ( 334#( stream . State , List ( types . Event ), Option ( types . Turn )), 335types . Error , 336) { 337let first = 338"{\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}" 339let late = "{\"choices\":[{\"index\":0,\"delta\":{\"content\":\"late\"}}]}" 340let assert Ok (#( state , _ , None )) = 341send ( stream . new ( types . ChatCompletions ), "" , first ) 342let assert Error ( types . InvalidEvent ( _ )) = send ( state , "" , late ) 343} 344 345pub fn responses_rejects_incomplete_call_in_completed_response_test () -> Result ( 346#( stream . State , List ( types . Event ), Option ( types . Turn )), 347types . Error , 348) { 349let event = 350"{\"type\":\"response.completed\",\"response\":{\"output\":[{\"type\":\"function_call\",\"call_id\":\"c\",\"name\":\"tool\",\"arguments\":\"{}\",\"status\":\"in_progress\"}]}}" 351let assert Error ( types . InvalidEvent ( _ )) = 352send ( stream . new ( types . Responses ), "" , event ) 353} 354 355pub fn compatible_reasoning_fields_are_preserved_without_renaming_test () -> Nil { 356let first = 357"{\"choices\":[{\"index\":0,\"delta\":{\"reasoning\":\"r1\",\"reasoning_content\":\"c1\"}}]}" 358let second = 359"{\"choices\":[{\"index\":0,\"delta\":{\"reasoning\":\"r2\",\"reasoning_content\":\"c2\",\"content\":\"answer\"},\"finish_reason\":\"stop\"}]}" 360let assert Ok (#( state , _ , None )) = 361send ( stream . new ( types . ChatCompletions ), "" , first ) 362let assert Ok (#( state , _ , None )) = send ( state , "" , second ) 363let assert Ok (#( _ , _ , Some ( turn ))) = send ( state , "" , "[DONE]" ) 364let assert [ item ] = turn . output 365assert types . inspect_item ( item , decode . at ([ "reasoning" ], decode . string )) 366== Ok ( "r1r2" ) 367assert types . inspect_item ( 368item , 369decode . at ([ "reasoning_content" ], decode . string ), 370) 371== Ok ( "c1c2" ) 372} 373 374pub fn reasoning_details_merge_fragments_by_index_and_keep_metadata_test () -> Nil { 375let first = 376"{\"choices\":[{\"index\":0,\"delta\":{\"reasoning\":\"a\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"index\":0,\"format\":\"unknown\",\"text\":\"a\"}]}}]}" 377let second = 378"{\"choices\":[{\"index\":0,\"delta\":{\"reasoning\":\"b\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"index\":0,\"format\":\"unknown\",\"text\":\"b\"}],\"content\":\"answer\"},\"finish_reason\":\"stop\"}]}" 379let assert Ok (#( state , _ , None )) = 380send ( stream . new ( types . ChatCompletions ), "" , first ) 381let assert Ok (#( state , _ , None )) = send ( state , "" , second ) 382let assert Ok (#( _ , _ , Some ( turn ))) = send ( state , "" , "[DONE]" ) 383let assert [ item ] = turn . output 384assert types . inspect_item ( 385item , 386decode . at ( 387[ "reasoning_details" ], 388decode . list ( decode . at ([ "text" ], decode . string )), 389), 390) 391== Ok ([ "ab" ]) 392assert types . inspect_item ( 393item , 394decode . at ( 395[ "reasoning_details" ], 396decode . list ( decode . at ([ "format" ], decode . string )), 397), 398) 399== Ok ([ "unknown" ]) 400} 401 402pub fn responses_uses_streamed_done_items_when_terminal_output_is_empty_test () -> Nil { 403let state = stream . new ( types . Responses ) 404let done = 405"{\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"type\":\"function_call\",\"call_id\":\"call_1\",\"name\":\"python\",\"arguments\":\"{\\\"code\\\":\\\"20 + 22\\\",\\\"timeout_ms\\\":1000}\",\"status\":\"completed\"}}" 406let assert Ok (#( state , [], None )) = send ( state , "" , done ) 407let completed = 408"{\"type\":\"response.completed\",\"response\":{\"id\":\"resp_1\",\"output\":[],\"usage\":{\"input_tokens\":10,\"output_tokens\":5}}}" 409let assert Ok (#( 410_ , 411[ types . Started ( "resp_1" )], 412Some ( types . Turn ( 413Some ( "resp_1" ), 414[ _ ], 415[ types . ToolCall ( "call_1" , "python" , arguments )], 416_ , 417types . ToolCalls , 418None , 419[#( "call_1" , 0 )], 420)), 421)) = send ( state , "" , completed ) 422assert arguments == "{\"code\":\"20 + 22\",\"timeout_ms\":1000}" 423} 424 425pub fn responses_places_events_by_item_id_when_the_server_omits_output_index_test () -> Nil { 426let message = 427json . object ([ 428#( "type" , json . string ( "message" )), 429#( "id" , json . string ( "msg_a" )), 430#( "role" , json . string ( "assistant" )), 431#( "status" , json . string ( "completed" )), 432#( 433"content" , 434json . array ( 435[ 436json . object ([ 437#( "type" , json . string ( "output_text" )), 438#( "text" , json . string ( "Hi" )), 439#( "annotations" , json . array ([], json . string )), 440]), 441], 442fn ( part ) { part }, 443), 444), 445]) 446let state = stream . new ( types . Responses ) 447let added = item_event ( "response.output_item.added" , "msg_a" , []) 448let assert Ok (#( state , [], None )) = send ( state , "" , added ) 449let delta = 450event ( "response.output_text.delta" , [ 451#( "item_id" , json . string ( "msg_a" )), 452#( "delta" , json . string ( "Hi" )), 453]) 454let assert Ok (#( state , [ types . TextDelta ( 0 , 0 , "Hi" )], None )) = 455send ( state , "" , delta ) 456let done = event ( "response.output_item.done" , [#( "item" , message )]) 457let assert Ok (#( state , [], None )) = send ( state , "" , done ) 458let completed = 459event ( "response.completed" , [ 460#( 461"response" , 462json . object ([ 463#( "id" , json . string ( "resp_1" )), 464#( "output" , json . array ([], json . string )), 465]), 466), 467]) 468let assert Ok (#( 469_ , 470[ types . Started ( "resp_1" )], 471Some ( types . Turn ( Some ( "resp_1" ), [ _ ], [], _ , types . Complete , None , [])), 472)) = send ( state , "" , completed ) 473Nil 474} 475 476pub fn responses_numbers_items_added_without_an_index_in_order_test () -> Nil { 477let state = stream . new ( types . Responses ) 478let assert Ok (#( state , [], None )) = 479send ( state , "" , item_event ( "response.output_item.added" , "msg_a" , [])) 480let call = [ 481#( "name" , json . string ( "get_weather" )), 482#( "call_id" , json . string ( "c" )), 483] 484let assert Ok (#( state , [], None )) = 485send ( 486state , 487"" , 488item_event ( "response.output_item.added" , "fc_b" , [ 489#( "type" , json . string ( "function_call" )), 490.. call 491]), 492) 493let delta = 494event ( "response.function_call_arguments.delta" , [ 495#( "item_id" , json . string ( "fc_b" )), 496#( "delta" , json . string ( "{}" )), 497]) 498let assert Ok (#( _ , [ types . ArgumentsDelta ( 1 , "get_weather" , "{}" )], None )) = 499send ( state , "" , delta ) 500Nil 501} 502 503pub fn responses_rejects_an_unplaceable_event_without_an_index_test () -> Nil { 504let delta = 505event ( "response.output_text.delta" , [ 506#( "item_id" , json . string ( "never_added" )), 507#( "delta" , json . string ( "Hi" )), 508]) 509let assert Error ( types . InvalidEvent ( _ )) = 510send ( stream . new ( types . Responses ), "" , delta ) 511let no_id = 512event ( "response.output_text.delta" , [#( "delta" , json . string ( "Hi" ))]) 513let assert Error ( types . InvalidEvent ( _ )) = 514send ( stream . new ( types . Responses ), "" , no_id ) 515Nil 516} 517 518fn event ( kind : String , fields : List (#( String , json . Json ))) -> String { 519json . object ([#( "type" , json . string ( kind )), .. fields ]) |> json . to_string 520} 521 522/// An item event as llama.cpp's server sends it: the item names itself, and 523/// there is no output_index. 524fn item_event ( 525kind : String , 526id : String , 527item_fields : List (#( String , json . Json )), 528) -> String { 529let item = case list . key_find ( item_fields , "type" ) { 530Ok ( _ ) -> item_fields 531Error ( _ ) -> [#( "type" , json . string ( "message" )), .. item_fields ] 532} 533event ( kind , [#( "item" , json . object ([#( "id" , json . string ( id )), .. item ]))]) 534} 535 536fn send ( 537state : stream . State , 538name : String , 539data : String , 540) -> Result (#( stream . State , List ( types . Event ), Option ( types . Turn )), types . Error ) { 541stream . feed ( state , sse . Event ( name , data )) 542}