1414// Note: anyhow is available if needed for future error handling
1515#[ allow( unused_imports) ]
1616use anyhow:: { Context , Result } ;
17+ use chrono:: { DateTime , Utc } ;
1718use serde:: { Deserialize , Serialize } ;
1819use serde_json:: json;
1920use std:: collections:: HashMap ;
@@ -613,7 +614,12 @@ pub struct TurnEndTotals {
613614/// Input used to build the structured `turn_end` observer payload.
614615pub struct TurnEndPayloadInput < ' a > {
615616 pub context : & ' a HookContext ,
616- pub turn_id : Option < & ' a str > ,
617+ pub created_at : DateTime < Utc > ,
618+ pub model_backed : bool ,
619+ pub provider : Option < & ' a str > ,
620+ pub billing_surface : Option < & ' a str > ,
621+ pub model : Option < & ' a str > ,
622+ pub turn_id : & ' a str ,
617623 pub status : & ' a str ,
618624 pub error : Option < & ' a str > ,
619625 pub duration : Duration ,
@@ -1291,7 +1297,11 @@ pub fn turn_end_payload(input: TurnEndPayloadInput<'_>) -> serde_json::Value {
12911297 "session_id" : input. context. session_id. as_deref( ) ,
12921298 "workspace" : input. context. workspace. as_ref( ) . map( |path| path. display( ) . to_string( ) ) ,
12931299 "mode" : input. context. mode. as_deref( ) ,
1294- "model" : input. context. model. as_deref( ) ,
1300+ "created_at" : input. created_at. to_rfc3339( ) ,
1301+ "model_backed" : input. model_backed,
1302+ "provider" : input. provider,
1303+ "billing_surface" : input. billing_surface,
1304+ "model" : input. model. or( input. context. model. as_deref( ) ) ,
12951305 "turn_id" : input. turn_id,
12961306 "status" : input. status,
12971307 "error" : input. error,
@@ -1576,7 +1586,12 @@ NOEQUAL line dropped
15761586
15771587 let payload = super :: turn_end_payload ( TurnEndPayloadInput {
15781588 context : & context,
1579- turn_id : Some ( "turn_123" ) ,
1589+ created_at : "2026-07-12T10:30:00Z" . parse ( ) . expect ( "timestamp" ) ,
1590+ model_backed : true ,
1591+ provider : Some ( "deepseek" ) ,
1592+ billing_surface : Some ( "test-payg" ) ,
1593+ model : Some ( "deepseek-v4-pro" ) ,
1594+ turn_id : "turn_123" ,
15801595 status : "completed" ,
15811596 error : None ,
15821597 duration : Duration :: from_millis ( 321 ) ,
@@ -1595,7 +1610,12 @@ NOEQUAL line dropped
15951610 assert_eq ! ( payload[ "session_id" ] , "sess_test" ) ;
15961611 assert_eq ! ( payload[ "workspace" ] , "/tmp/codewhale" ) ;
15971612 assert_eq ! ( payload[ "mode" ] , "agent" ) ;
1598- assert_eq ! ( payload[ "model" ] , "deepseek-v4" ) ;
1613+ assert_eq ! ( payload[ "created_at" ] , "2026-07-12T10:30:00+00:00" ) ;
1614+ assert_eq ! ( payload[ "model_backed" ] , true ) ;
1615+ assert_eq ! ( payload[ "provider" ] , "deepseek" ) ;
1616+ assert_eq ! ( payload[ "billing_surface" ] , "test-payg" ) ;
1617+ assert ! ( payload. get( "base_url" ) . is_none( ) ) ;
1618+ assert_eq ! ( payload[ "model" ] , "deepseek-v4-pro" ) ;
15991619 assert_eq ! ( payload[ "turn_id" ] , "turn_123" ) ;
16001620 assert_eq ! ( payload[ "status" ] , "completed" ) ;
16011621 assert_eq ! ( payload[ "error" ] , serde_json:: Value :: Null ) ;
@@ -1883,7 +1903,12 @@ printf '%s\n' '{{"text":"stdout is not a mutation contract"}}'
18831903 let context = submit_context ( & dir) . with_tokens ( 15 ) ;
18841904 let payload = super :: turn_end_payload ( TurnEndPayloadInput {
18851905 context : & context,
1886- turn_id : Some ( "turn_observed" ) ,
1906+ created_at : "2026-07-12T10:30:00Z" . parse ( ) . expect ( "timestamp" ) ,
1907+ model_backed : true ,
1908+ provider : Some ( "openai" ) ,
1909+ billing_surface : None ,
1910+ model : Some ( "gpt-5.5" ) ,
1911+ turn_id : "turn_observed" ,
18871912 status : "completed" ,
18881913 error : None ,
18891914 duration : Duration :: from_millis ( 7 ) ,
@@ -1912,6 +1937,9 @@ printf '%s\n' '{{"text":"stdout is not a mutation contract"}}'
19121937 serde_json:: from_str ( & std:: fs:: read_to_string ( out) . expect ( "payload written" ) )
19131938 . expect ( "valid JSON payload" ) ;
19141939 assert_eq ! ( captured[ "event" ] , "turn_end" ) ;
1940+ assert_eq ! ( captured[ "created_at" ] , "2026-07-12T10:30:00+00:00" ) ;
1941+ assert_eq ! ( captured[ "provider" ] , "openai" ) ;
1942+ assert_eq ! ( captured[ "model" ] , "gpt-5.5" ) ;
19151943 assert_eq ! ( captured[ "turn_id" ] , "turn_observed" ) ;
19161944 assert_eq ! ( captured[ "totals" ] [ "input_tokens" ] , 12 ) ;
19171945 assert_eq ! ( captured[ "totals" ] [ "output_tokens" ] , 3 ) ;
0 commit comments