@@ -30,18 +30,21 @@ async fn test_list_and_resume_conversations() {
3030        "2025-01-02T12-00-00" , 
3131        "2025-01-02T12:00:00Z" , 
3232        "Hello A" , 
33+         Some ( "openai" ) , 
3334    ) ; 
3435    create_fake_rollout ( 
3536        codex_home. path ( ) , 
3637        "2025-01-01T13-00-00" , 
3738        "2025-01-01T13:00:00Z" , 
3839        "Hello B" , 
40+         Some ( "openai" ) , 
3941    ) ; 
4042    create_fake_rollout ( 
4143        codex_home. path ( ) , 
4244        "2025-01-01T12-00-00" , 
4345        "2025-01-01T12:00:00Z" , 
4446        "Hello C" , 
47+         None , 
4548    ) ; 
4649
4750    let  mut  mcp = McpProcess :: new ( codex_home. path ( ) ) 
@@ -57,6 +60,7 @@ async fn test_list_and_resume_conversations() {
5760        . send_list_conversations_request ( ListConversationsParams  { 
5861            page_size :  Some ( 2 ) , 
5962            cursor :  None , 
63+             model_providers :  None , 
6064        } ) 
6165        . await 
6266        . expect ( "send listConversations" ) ; 
@@ -74,6 +78,8 @@ async fn test_list_and_resume_conversations() {
7478    // Newest first; preview text should match 
7579    assert_eq ! ( items[ 0 ] . preview,  "Hello A" ) ; 
7680    assert_eq ! ( items[ 1 ] . preview,  "Hello B" ) ; 
81+     assert_eq ! ( items[ 0 ] . model_provider,  "openai" ) ; 
82+     assert_eq ! ( items[ 1 ] . model_provider,  "openai" ) ; 
7783    assert ! ( items[ 0 ] . path. is_absolute( ) ) ; 
7884    assert ! ( next_cursor. is_some( ) ) ; 
7985
@@ -82,6 +88,7 @@ async fn test_list_and_resume_conversations() {
8288        . send_list_conversations_request ( ListConversationsParams  { 
8389            page_size :  Some ( 2 ) , 
8490            cursor :  next_cursor, 
91+             model_providers :  None , 
8592        } ) 
8693        . await 
8794        . expect ( "send listConversations page 2" ) ; 
@@ -99,7 +106,88 @@ async fn test_list_and_resume_conversations() {
99106    }  = to_response :: < ListConversationsResponse > ( resp2) . expect ( "deserialize response" ) ; 
100107    assert_eq ! ( items2. len( ) ,  1 ) ; 
101108    assert_eq ! ( items2[ 0 ] . preview,  "Hello C" ) ; 
102-     assert ! ( next2. is_some( ) ) ; 
109+     assert_eq ! ( items2[ 0 ] . model_provider,  "openai" ) ; 
110+     assert_eq ! ( next2,  None ) ; 
111+ 
112+     // Add a conversation with an explicit non-OpenAI provider for filter tests. 
113+     create_fake_rollout ( 
114+         codex_home. path ( ) , 
115+         "2025-01-01T11-30-00" , 
116+         "2025-01-01T11:30:00Z" , 
117+         "Hello TP" , 
118+         Some ( "test-provider" ) , 
119+     ) ; 
120+ 
121+     // Filtering by model provider should return only matching sessions. 
122+     let  filter_req_id = mcp
123+         . send_list_conversations_request ( ListConversationsParams  { 
124+             page_size :  Some ( 10 ) , 
125+             cursor :  None , 
126+             model_providers :  Some ( vec ! [ "test-provider" . to_string( ) ] ) , 
127+         } ) 
128+         . await 
129+         . expect ( "send listConversations filtered" ) ; 
130+     let  filter_resp:  JSONRPCResponse  = timeout ( 
131+         DEFAULT_READ_TIMEOUT , 
132+         mcp. read_stream_until_response_message ( RequestId :: Integer ( filter_req_id) ) , 
133+     ) 
134+     . await 
135+     . expect ( "listConversations filtered timeout" ) 
136+     . expect ( "listConversations filtered resp" ) ; 
137+     let  ListConversationsResponse  { 
138+         items :  filtered_items, 
139+         next_cursor :  filtered_next, 
140+     }  = to_response :: < ListConversationsResponse > ( filter_resp) . expect ( "deserialize filtered" ) ; 
141+     assert_eq ! ( filtered_items. len( ) ,  1 ) ; 
142+     assert_eq ! ( filtered_next,  None ) ; 
143+     assert_eq ! ( filtered_items[ 0 ] . preview,  "Hello TP" ) ; 
144+     assert_eq ! ( filtered_items[ 0 ] . model_provider,  "test-provider" ) ; 
145+ 
146+     // Empty filter should include every session regardless of provider metadata. 
147+     let  unfiltered_req_id = mcp
148+         . send_list_conversations_request ( ListConversationsParams  { 
149+             page_size :  Some ( 10 ) , 
150+             cursor :  None , 
151+             model_providers :  Some ( Vec :: new ( ) ) , 
152+         } ) 
153+         . await 
154+         . expect ( "send listConversations unfiltered" ) ; 
155+     let  unfiltered_resp:  JSONRPCResponse  = timeout ( 
156+         DEFAULT_READ_TIMEOUT , 
157+         mcp. read_stream_until_response_message ( RequestId :: Integer ( unfiltered_req_id) ) , 
158+     ) 
159+     . await 
160+     . expect ( "listConversations unfiltered timeout" ) 
161+     . expect ( "listConversations unfiltered resp" ) ; 
162+     let  ListConversationsResponse  { 
163+         items :  unfiltered_items, 
164+         next_cursor :  unfiltered_next, 
165+     }  = to_response :: < ListConversationsResponse > ( unfiltered_resp) 
166+         . expect ( "deserialize unfiltered response" ) ; 
167+     assert_eq ! ( unfiltered_items. len( ) ,  4 ) ; 
168+     assert ! ( unfiltered_next. is_none( ) ) ; 
169+ 
170+     let  empty_req_id = mcp
171+         . send_list_conversations_request ( ListConversationsParams  { 
172+             page_size :  Some ( 10 ) , 
173+             cursor :  None , 
174+             model_providers :  Some ( vec ! [ "other" . to_string( ) ] ) , 
175+         } ) 
176+         . await 
177+         . expect ( "send listConversations filtered empty" ) ; 
178+     let  empty_resp:  JSONRPCResponse  = timeout ( 
179+         DEFAULT_READ_TIMEOUT , 
180+         mcp. read_stream_until_response_message ( RequestId :: Integer ( empty_req_id) ) , 
181+     ) 
182+     . await 
183+     . expect ( "listConversations filtered empty timeout" ) 
184+     . expect ( "listConversations filtered empty resp" ) ; 
185+     let  ListConversationsResponse  { 
186+         items :  empty_items, 
187+         next_cursor :  empty_next, 
188+     }  = to_response :: < ListConversationsResponse > ( empty_resp) . expect ( "deserialize filtered empty" ) ; 
189+     assert ! ( empty_items. is_empty( ) ) ; 
190+     assert ! ( empty_next. is_none( ) ) ; 
103191
104192    // Now resume one of the sessions and expect a SessionConfigured notification and response. 
105193    let  resume_req_id = mcp
@@ -152,7 +240,13 @@ async fn test_list_and_resume_conversations() {
152240    assert ! ( !conversation_id. to_string( ) . is_empty( ) ) ; 
153241} 
154242
155- fn  create_fake_rollout ( codex_home :  & Path ,  filename_ts :  & str ,  meta_rfc3339 :  & str ,  preview :  & str )  { 
243+ fn  create_fake_rollout ( 
244+     codex_home :  & Path , 
245+     filename_ts :  & str , 
246+     meta_rfc3339 :  & str , 
247+     preview :  & str , 
248+     model_provider :  Option < & str > , 
249+ )  { 
156250    let  uuid = Uuid :: new_v4 ( ) ; 
157251    // sessions/YYYY/MM/DD/ derived from filename_ts (YYYY-MM-DDThh-mm-ss) 
158252    let  year = & filename_ts[ 0 ..4 ] ; 
@@ -164,18 +258,22 @@ fn create_fake_rollout(codex_home: &Path, filename_ts: &str, meta_rfc3339: &str,
164258    let  file_path = dir. join ( format ! ( "rollout-{filename_ts}-{uuid}.jsonl" ) ) ; 
165259    let  mut  lines = Vec :: new ( ) ; 
166260    // Meta line with timestamp (flattened meta in payload for new schema) 
261+     let  mut  payload = json ! ( { 
262+         "id" :  uuid, 
263+         "timestamp" :  meta_rfc3339, 
264+         "cwd" :  "/" , 
265+         "originator" :  "codex" , 
266+         "cli_version" :  "0.0.0" , 
267+         "instructions" :  null, 
268+     } ) ; 
269+     if  let  Some ( provider)  = model_provider { 
270+         payload[ "model_provider" ]  = json ! ( provider) ; 
271+     } 
167272    lines. push ( 
168273        json ! ( { 
169274            "timestamp" :  meta_rfc3339, 
170275            "type" :  "session_meta" , 
171-             "payload" :  { 
172-                 "id" :  uuid, 
173-                 "timestamp" :  meta_rfc3339, 
174-                 "cwd" :  "/" , 
175-                 "originator" :  "codex" , 
176-                 "cli_version" :  "0.0.0" , 
177-                 "instructions" :  null
178-             } 
276+             "payload" :  payload
179277        } ) 
180278        . to_string ( ) , 
181279    ) ; 
0 commit comments