@@ -110,13 +110,24 @@ pub(crate) async fn start_thread_core(
110110 workspace_id : String ,
111111) -> Result < Value , String > {
112112 let session = get_session_clone ( sessions, & workspace_id) . await ?;
113+
114+ // Reuse the pre-warmed session if available (created eagerly on workspace
115+ // connect to populate the models cache ahead of the first prompt).
116+ if let Some ( session_id) = session. prewarmed_session_id . lock ( ) . await . take ( ) {
117+ let mut ts = session. translation_state . lock ( ) . await ;
118+ ts. session_id = session_id. clone ( ) ;
119+ return Ok ( json ! ( {
120+ "result" : {
121+ "thread" : { "id" : session_id }
122+ }
123+ } ) ) ;
124+ }
125+
113126 let params = json ! ( {
114127 "cwd" : session. entry. path,
115128 "mcpServers" : [ ]
116129 } ) ;
117130 let response = session. send_request ( "session/new" , params) . await ?;
118- // ACP returns { sessionId, models, modes, ... }.
119- // Frontend expects { result: { thread: { id: "..." } } }.
120131 let session_id = response
121132 . get ( "result" )
122133 . and_then ( |r| r. get ( "sessionId" ) )
@@ -1045,6 +1056,34 @@ pub(crate) async fn start_review_core(
10451056 Err ( "review is not supported by OpenCode ACP" . to_string ( ) )
10461057}
10471058
1059+ const THINKING_LEVELS : & [ & str ] = & [ "low" , "medium" , "high" , "max" ] ;
1060+
1061+ /// Returns `Some((base_name, level))` when `name` ends with ` (<known_level>)`.
1062+ fn strip_thinking_suffix ( name : & str ) -> Option < ( & str , & str ) > {
1063+ let name = name. trim ( ) ;
1064+ let rest = name. strip_suffix ( ')' ) ?;
1065+ let paren_start = rest. rfind ( " (" ) ?;
1066+ let level = & rest[ paren_start + 2 ..] ;
1067+ if THINKING_LEVELS
1068+ . iter ( )
1069+ . any ( |tl| tl. eq_ignore_ascii_case ( level) )
1070+ {
1071+ Some ( ( name[ ..paren_start] . trim ( ) , level) )
1072+ } else {
1073+ None
1074+ }
1075+ }
1076+
1077+ fn thinking_level_order ( level : & str ) -> usize {
1078+ match level. to_ascii_lowercase ( ) . as_str ( ) {
1079+ "low" => 0 ,
1080+ "medium" => 1 ,
1081+ "high" => 2 ,
1082+ "max" => 3 ,
1083+ _ => 99 ,
1084+ }
1085+ }
1086+
10481087pub ( crate ) async fn model_list_core (
10491088 sessions : & Mutex < HashMap < String , Arc < WorkspaceSession > > > ,
10501089 workspace_id : String ,
@@ -1065,6 +1104,31 @@ pub(crate) async fn model_list_core(
10651104 . cloned ( )
10661105 . unwrap_or_default ( ) ;
10671106
1107+ // Pass 1 — collect thinking-variant effort levels keyed by base display
1108+ // name, and record which modelIds are variants so they can be skipped.
1109+ let mut variant_levels: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
1110+ let mut variant_model_ids: HashSet < String > = HashSet :: new ( ) ;
1111+
1112+ for entry in & available_models {
1113+ let display_name = entry
1114+ . get ( "name" )
1115+ . and_then ( |v| v. as_str ( ) )
1116+ . unwrap_or_default ( ) ;
1117+ if let Some ( ( base_name, level) ) = strip_thinking_suffix ( display_name) {
1118+ variant_levels
1119+ . entry ( base_name. to_string ( ) )
1120+ . or_default ( )
1121+ . push ( level. to_string ( ) ) ;
1122+ if let Some ( mid) = entry. get ( "modelId" ) . and_then ( |v| v. as_str ( ) ) {
1123+ variant_model_ids. insert ( mid. trim ( ) . to_string ( ) ) ;
1124+ }
1125+ }
1126+ }
1127+ for levels in variant_levels. values_mut ( ) {
1128+ levels. sort_by_key ( |l| thinking_level_order ( l) ) ;
1129+ }
1130+
1131+ // Pass 2 — emit base models only, with effort levels attached.
10681132 let data: Vec < Value > = available_models
10691133 . into_iter ( )
10701134 . filter_map ( |entry| {
@@ -1074,7 +1138,7 @@ pub(crate) async fn model_list_core(
10741138 . unwrap_or_default ( )
10751139 . trim ( )
10761140 . to_string ( ) ;
1077- if model_id. is_empty ( ) {
1141+ if model_id. is_empty ( ) || variant_model_ids . contains ( & model_id ) {
10781142 return None ;
10791143 }
10801144 let display_name = entry
@@ -1084,13 +1148,47 @@ pub(crate) async fn model_list_core(
10841148 . trim ( )
10851149 . to_string ( ) ;
10861150
1151+ // Prefer explicit ACP field; fall back to levels scraped from
1152+ // variant entries whose base name matches this model.
1153+ let acp_efforts = entry
1154+ . get ( "supportedReasoningEfforts" )
1155+ . and_then ( |v| v. as_array ( ) )
1156+ . filter ( |a| !a. is_empty ( ) ) ;
1157+
1158+ let efforts: Vec < Value > = if let Some ( acp) = acp_efforts {
1159+ acp. iter ( )
1160+ . map ( |e| {
1161+ if e. is_object ( ) {
1162+ e. clone ( )
1163+ } else {
1164+ let label = e. as_str ( ) . unwrap_or_default ( ) ;
1165+ json ! ( { "reasoningEffort" : label, "description" : "" } )
1166+ }
1167+ } )
1168+ . collect ( )
1169+ } else {
1170+ variant_levels
1171+ . get ( & display_name)
1172+ . into_iter ( )
1173+ . flatten ( )
1174+ . map ( |level| json ! ( { "reasoningEffort" : level, "description" : "" } ) )
1175+ . collect ( )
1176+ } ;
1177+
1178+ let default_effort = entry
1179+ . get ( "defaultReasoningEffort" )
1180+ . and_then ( |v| v. as_str ( ) )
1181+ . filter ( |s| !s. trim ( ) . is_empty ( ) )
1182+ . map ( |s| Value :: String ( s. trim ( ) . to_string ( ) ) )
1183+ . unwrap_or ( Value :: Null ) ;
1184+
10871185 Some ( json ! ( {
10881186 "id" : model_id. clone( ) ,
10891187 "model" : model_id. clone( ) ,
10901188 "displayName" : display_name,
10911189 "description" : "" ,
1092- "supportedReasoningEfforts" : [ ] ,
1093- "defaultReasoningEffort" : null ,
1190+ "supportedReasoningEfforts" : efforts ,
1191+ "defaultReasoningEffort" : default_effort ,
10941192 "isDefault" : model_id == current_model,
10951193 } ) )
10961194 } )
0 commit comments