@@ -121,3 +121,107 @@ async fn test_acp_stream_with_mock_agent() {
121121 . any ( |e| matches ! ( e, ResponseEvent :: Completed { .. } ) ) ;
122122 assert ! ( completed, "Should receive Completed event" ) ;
123123}
124+
125+ #[ tokio:: test]
126+ async fn test_acp_event_ordering ( ) {
127+ // Create ACP provider for mock-acp-agent
128+ let provider = ModelProviderInfo {
129+ name : "mock-acp" . into ( ) ,
130+ base_url : None ,
131+ env_key : None ,
132+ env_key_instructions : None ,
133+ experimental_bearer_token : None ,
134+ wire_api : WireApi :: Acp ,
135+ query_params : None ,
136+ http_headers : None ,
137+ env_http_headers : None ,
138+ request_max_retries : Some ( 0 ) ,
139+ stream_max_retries : Some ( 0 ) ,
140+ stream_idle_timeout_ms : Some ( 5_000 ) ,
141+ requires_openai_auth : false ,
142+ } ;
143+
144+ // Load default config
145+ let codex_home = TempDir :: new ( ) . expect ( "Failed to create temp dir" ) ;
146+ let mut config = load_default_config_for_test ( & codex_home) ;
147+ config. model = "mock-model" . to_string ( ) ;
148+ config. model_provider_id = provider. name . clone ( ) ;
149+ config. model_provider = provider. clone ( ) ;
150+ let effort = config. model_reasoning_effort ;
151+ let summary = config. model_reasoning_summary ;
152+ let config = Arc :: new ( config) ;
153+
154+ let conversation_id = ConversationId :: new ( ) ;
155+
156+ let otel_event_manager = OtelEventManager :: new (
157+ conversation_id,
158+ config. model . as_str ( ) ,
159+ config. model_family . slug . as_str ( ) ,
160+ None ,
161+ Some ( "test@test.com" . to_string ( ) ) ,
162+ Some ( AuthMode :: ChatGPT ) ,
163+ false ,
164+ "test" . to_string ( ) ,
165+ ) ;
166+
167+ let client = ModelClient :: new (
168+ Arc :: clone ( & config) ,
169+ None ,
170+ otel_event_manager,
171+ provider,
172+ effort,
173+ summary,
174+ conversation_id,
175+ SessionSource :: Exec ,
176+ ) ;
177+
178+ let mut prompt = Prompt :: default ( ) ;
179+ prompt. input = vec ! [ ResponseItem :: Message {
180+ id: None ,
181+ role: "user" . to_string( ) ,
182+ content: vec![ ContentItem :: InputText {
183+ text: "Hello" . to_string( ) ,
184+ } ] ,
185+ } ] ;
186+
187+ // Stream response
188+ let mut stream = client. stream ( & prompt) . await . expect ( "Stream should start" ) ;
189+
190+ // Collect events
191+ let mut events = Vec :: new ( ) ;
192+ while let Some ( event_result) = stream. next ( ) . await {
193+ let event = event_result. expect ( "Event should not be error" ) ;
194+ events. push ( event) ;
195+ }
196+
197+ // Verify event ordering follows Created -> OutputItemAdded -> Deltas pattern
198+ assert ! ( !events. is_empty( ) , "Should receive events from mock agent" ) ;
199+
200+ // First event should be Created
201+ assert ! (
202+ matches!( events[ 0 ] , ResponseEvent :: Created ) ,
203+ "First event should be Created, got: {:?}" ,
204+ events[ 0 ]
205+ ) ;
206+
207+ // Find first OutputItemAdded event
208+ let output_item_added_index = events
209+ . iter ( )
210+ . position ( |e| matches ! ( e, ResponseEvent :: OutputItemAdded ( _) ) )
211+ . expect ( "Should have OutputItemAdded event" ) ;
212+
213+ // OutputItemAdded should come before any deltas
214+ for ( i, event) in events. iter ( ) . enumerate ( ) {
215+ match event {
216+ ResponseEvent :: OutputTextDelta ( _) | ResponseEvent :: ReasoningContentDelta { .. } => {
217+ assert ! (
218+ i > output_item_added_index,
219+ "Delta event at index {} should come after OutputItemAdded at index {}" ,
220+ i,
221+ output_item_added_index
222+ ) ;
223+ }
224+ _ => { }
225+ }
226+ }
227+ }
0 commit comments