@@ -666,25 +666,40 @@ async fn admin_refresh(State(state): State<AppState>, headers: HeaderMap) -> Res
666666
667667 let mut projects = state. projects . write ( ) . unwrap ( ) ;
668668 let mut added = Vec :: new ( ) ;
669- let mut skipped = Vec :: new ( ) ;
669+ let mut already_loaded = Vec :: new ( ) ;
670+ let mut failed = Vec :: new ( ) ;
671+
672+ // Remove projects that no longer exist in the registry.
673+ let mut removed = Vec :: new ( ) ;
674+ let to_remove: Vec < String > = projects
675+ . keys ( )
676+ . filter ( |id| !registry. projects . contains_key ( id. as_str ( ) ) )
677+ . cloned ( )
678+ . collect ( ) ;
679+ for id in & to_remove {
680+ projects. remove ( id) ;
681+ removed. push ( id. clone ( ) ) ;
682+ }
670683
671684 for ( project_id, entry) in & registry. projects {
672685 if projects. contains_key ( project_id) {
673- skipped . push ( project_id. clone ( ) ) ;
686+ already_loaded . push ( project_id. clone ( ) ) ;
674687 continue ;
675688 }
676689
677690 let root = PathBuf :: from ( & entry. root ) ;
678691 let env_path = root. join ( PROJECT_ENV_FILENAME ) ;
679692 if !env_path. exists ( ) {
680693 tracing:: warn!( "refresh: project {project_id} missing env file, skipping" ) ;
694+ failed. push ( json ! ( { "project_id" : project_id, "reason" : "missing env file" } ) ) ;
681695 continue ;
682696 }
683697
684698 let project_env = match read_project_env ( & env_path) {
685699 Ok ( e) => e,
686700 Err ( e) => {
687701 tracing:: warn!( "refresh: cannot read env for {project_id}: {e}" ) ;
702+ failed. push ( json ! ( { "project_id" : project_id, "reason" : format!( "read env failed: {e}" ) } ) ) ;
688703 continue ;
689704 }
690705 } ;
@@ -694,6 +709,7 @@ async fn admin_refresh(State(state): State<AppState>, headers: HeaderMap) -> Res
694709 Ok ( c) => c,
695710 Err ( e) => {
696711 tracing:: warn!( "refresh: bad config for {project_id}: {e}" ) ;
712+ failed. push ( json ! ( { "project_id" : project_id, "reason" : format!( "bad config: {e}" ) } ) ) ;
697713 continue ;
698714 }
699715 } ;
@@ -705,6 +721,7 @@ async fn admin_refresh(State(state): State<AppState>, headers: HeaderMap) -> Res
705721 Ok ( s) => s,
706722 Err ( e) => {
707723 tracing:: warn!( "refresh: cannot create state for {project_id}: {e}" ) ;
724+ failed. push ( json ! ( { "project_id" : project_id, "reason" : format!( "state init failed: {e}" ) } ) ) ;
708725 continue ;
709726 }
710727 } ;
@@ -716,6 +733,7 @@ async fn admin_refresh(State(state): State<AppState>, headers: HeaderMap) -> Res
716733 Ok ( c) => c,
717734 Err ( e) => {
718735 tracing:: warn!( "refresh: cannot create client for {project_id}: {e}" ) ;
736+ failed. push ( json ! ( { "project_id" : project_id, "reason" : format!( "client init failed: {e}" ) } ) ) ;
719737 continue ;
720738 }
721739 } ;
@@ -730,7 +748,7 @@ async fn admin_refresh(State(state): State<AppState>, headers: HeaderMap) -> Res
730748 added. push ( project_id. clone ( ) ) ;
731749 }
732750
733- Json ( json ! ( { "status" : "ok" , "added" : added, "already_loaded" : skipped } ) ) . into_response ( )
751+ Json ( json ! ( { "status" : "ok" , "added" : added, "already_loaded" : already_loaded , "removed" : removed , "failed" : failed } ) ) . into_response ( )
734752}
735753
736754fn parse_routed_model ( model : & str ) -> Result < ( String , String ) , & ' static str > {
@@ -810,13 +828,23 @@ fn early_stream_failed_response(
810828 kind : & ' static str ,
811829 message : & str ,
812830) -> Response {
831+ let response_id = format ! ( "resp_{}" , Uuid :: new_v4( ) . simple( ) ) ;
832+ let created_at = now_ts ( ) ;
833+ let shell = json ! ( { "id" : response_id, "object" : "response" , "created_at" : created_at, "status" : "in_progress" , "error" : null, "incomplete_details" : null, "instructions" : body. get( "instructions" ) . cloned( ) . unwrap_or( Value :: Null ) , "model" : model_alias, "output" : [ ] , "parallel_tool_calls" : body. get( "parallel_tool_calls" ) . and_then( Value :: as_bool) . unwrap_or( false ) , "previous_response_id" : body. get( "previous_response_id" ) . cloned( ) . unwrap_or( Value :: Null ) , "store" : false , "usage" : { "input_tokens" : 0 , "output_tokens" : 0 , "total_tokens" : 0 } , "metadata" : body. get( "metadata" ) . cloned( ) . unwrap_or_else( || json!( { } ) ) } ) ;
813834 let ( tx, rx) =
814835 tokio:: sync:: mpsc:: unbounded_channel :: < Result < axum:: response:: sse:: Event , Infallible > > ( ) ;
815- let response = responses_failed_value ( & body, & model_alias, kind, message) ;
816- let event = json ! ( { "type" : "response.failed" , "response" : response} ) ;
836+ let _ = tx. send ( Ok ( axum:: response:: sse:: Event :: default ( )
837+ . event ( "response.created" )
838+ . data ( json ! ( { "type" : "response.created" , "response" : shell. clone( ) } ) . to_string ( ) ) ) ) ;
839+ let _ = tx. send ( Ok ( axum:: response:: sse:: Event :: default ( )
840+ . event ( "response.in_progress" )
841+ . data ( json ! ( { "type" : "response.in_progress" , "response" : shell. clone( ) } ) . to_string ( ) ) ) ) ;
842+ let mut failed_response = shell;
843+ failed_response[ "status" ] = json ! ( "failed" ) ;
844+ failed_response[ "error" ] = json ! ( { "type" : kind, "code" : kind, "message" : message. chars( ) . take( 1000 ) . collect:: <String >( ) } ) ;
817845 let _ = tx. send ( Ok ( axum:: response:: sse:: Event :: default ( )
818846 . event ( "response.failed" )
819- . data ( event . to_string ( ) ) ) ) ;
847+ . data ( json ! ( { "type" : "response.failed" , "response" : failed_response } ) . to_string ( ) ) ) ) ;
820848 let _ = tx. send ( Ok ( axum:: response:: sse:: Event :: default ( ) . data ( "[DONE]" ) ) ) ;
821849 let stream = tokio_stream:: wrappers:: UnboundedReceiverStream :: new ( rx) ;
822850 Sse :: new ( stream) . into_response ( )
0 commit comments