Call models endpoint in models manager (#7616)

- Introduce `with_remote_overrides` and update
`refresh_available_models`
- Put `auth_manager` instead of `auth_mode` on `models_manager`
- Remove `ShellType` and `ReasoningLevel` to use already existing
structs
This commit is contained in:
Ahmed Ibrahim
2025-12-04 18:28:03 -08:00
committed by GitHub
Unverified
parent 6736d1828d
commit 7b359c9c8e
13 changed files with 329 additions and 88 deletions
@@ -5,11 +5,11 @@ use codex_api::provider::RetryConfig;
use codex_api::provider::WireApi;
use codex_client::ReqwestTransport;
use codex_protocol::openai_models::ClientVersion;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelVisibility;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::ReasoningLevel;
use codex_protocol::openai_models::ShellType;
use codex_protocol::openai_models::ReasoningEffort;
use http::HeaderMap;
use http::Method;
use wiremock::Mock;
@@ -55,13 +55,13 @@ async fn models_client_hits_models_endpoint() {
slug: "gpt-test".to_string(),
display_name: "gpt-test".to_string(),
description: Some("desc".to_string()),
default_reasoning_level: ReasoningLevel::Medium,
default_reasoning_level: ReasoningEffort::Medium,
supported_reasoning_levels: vec![
ReasoningLevel::Low,
ReasoningLevel::Medium,
ReasoningLevel::High,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
],
shell_type: ShellType::ShellCommand,
shell_type: ConfigShellToolType::ShellCommand,
visibility: ModelVisibility::List,
minimal_client_version: ClientVersion(0, 1, 0),
supported_in_api: true,
+8 -13
View File
@@ -2475,6 +2475,7 @@ pub(crate) use tests::make_session_and_context_with_rx;
#[cfg(test)]
mod tests {
use super::*;
use crate::CodexAuth;
use crate::config::ConfigOverrides;
use crate::config::ConfigToml;
use crate::exec::ExecToolCallOutput;
@@ -2765,12 +2766,9 @@ mod tests {
.expect("load default test config");
let config = Arc::new(config);
let conversation_id = ConversationId::default();
let auth_manager = AuthManager::shared(
config.cwd.clone(),
false,
config.cli_auth_credentials_store_mode,
);
let models_manager = Arc::new(ModelsManager::new(auth_manager.get_auth_mode()));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
let otel_event_manager =
otel_event_manager(conversation_id, config.as_ref(), &models_manager);
@@ -2801,7 +2799,7 @@ mod tests {
rollout: Mutex::new(None),
user_shell: default_user_shell(),
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
auth_manager: Arc::clone(&auth_manager),
auth_manager: auth_manager.clone(),
otel_event_manager: otel_event_manager.clone(),
models_manager: models_manager.clone(),
tool_approvals: Mutex::new(ApprovalStore::default()),
@@ -2847,12 +2845,9 @@ mod tests {
.expect("load default test config");
let config = Arc::new(config);
let conversation_id = ConversationId::default();
let auth_manager = AuthManager::shared(
config.cwd.clone(),
false,
config.cli_auth_credentials_store_mode,
);
let models_manager = Arc::new(ModelsManager::new(auth_manager.get_auth_mode()));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
let otel_event_manager =
otel_event_manager(conversation_id, config.as_ref(), &models_manager);
+1 -1
View File
@@ -47,7 +47,7 @@ impl ConversationManager {
conversations: Arc::new(RwLock::new(HashMap::new())),
auth_manager: auth_manager.clone(),
session_source,
models_manager: Arc::new(ModelsManager::new(auth_manager.get_auth_mode())),
models_manager: Arc::new(ModelsManager::new(auth_manager)),
}
}
@@ -1,11 +1,12 @@
use codex_protocol::config_types::Verbosity;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ReasoningEffort;
use crate::config::Config;
use crate::config::types::ReasoningSummaryFormat;
use crate::tools::handlers::apply_patch::ApplyPatchToolType;
use crate::tools::spec::ConfigShellToolType;
use crate::truncate::TruncationPolicy;
use codex_protocol::openai_models::ConfigShellToolType;
/// The `instructions` field in the payload sent to a model should always start
/// with this content.
@@ -83,6 +84,15 @@ impl ModelFamily {
}
self
}
pub fn with_remote_overrides(mut self, remote_models: Vec<ModelInfo>) -> Self {
for model in remote_models {
if model.slug == self.slug {
self.default_reasoning_effort = Some(model.default_reasoning_level);
self.shell_type = model.shell_type;
}
}
self
}
}
macro_rules! model_family {
@@ -275,3 +285,71 @@ fn derive_default_model_family(model: &str) -> ModelFamily {
truncation_policy: TruncationPolicy::Bytes(10_000),
}
}
#[cfg(test)]
mod tests {
use super::*;
use codex_protocol::openai_models::ClientVersion;
use codex_protocol::openai_models::ModelVisibility;
fn remote(slug: &str, effort: ReasoningEffort, shell: ConfigShellToolType) -> ModelInfo {
ModelInfo {
slug: slug.to_string(),
display_name: slug.to_string(),
description: Some(format!("{slug} desc")),
default_reasoning_level: effort,
supported_reasoning_levels: vec![effort],
shell_type: shell,
visibility: ModelVisibility::List,
minimal_client_version: ClientVersion(0, 1, 0),
supported_in_api: true,
priority: 1,
}
}
#[test]
fn remote_overrides_apply_when_slug_matches() {
let family = model_family!("gpt-4o-mini", "gpt-4o-mini");
assert_ne!(family.default_reasoning_effort, Some(ReasoningEffort::High));
let updated = family.with_remote_overrides(vec![
remote(
"gpt-4o-mini",
ReasoningEffort::High,
ConfigShellToolType::ShellCommand,
),
remote(
"other-model",
ReasoningEffort::Low,
ConfigShellToolType::UnifiedExec,
),
]);
assert_eq!(
updated.default_reasoning_effort,
Some(ReasoningEffort::High)
);
assert_eq!(updated.shell_type, ConfigShellToolType::ShellCommand);
}
#[test]
fn remote_overrides_skip_non_matching_models() {
let family = model_family!(
"codex-mini-latest",
"codex-mini-latest",
shell_type: ConfigShellToolType::Local
);
let updated = family.clone().with_remote_overrides(vec![remote(
"other",
ReasoningEffort::High,
ConfigShellToolType::ShellCommand,
)]);
assert_eq!(
updated.default_reasoning_effort,
family.default_reasoning_effort
);
assert_eq!(updated.shell_type, family.shell_type);
}
}
@@ -1,34 +1,172 @@
use codex_app_server_protocol::AuthMode;
use codex_api::ModelsClient;
use codex_api::ReqwestTransport;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelPreset;
use http::HeaderMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::api_bridge::auth_provider_from_auth;
use crate::api_bridge::map_api_error;
use crate::auth::AuthManager;
use crate::config::Config;
use crate::default_client::build_reqwest_client;
use crate::error::Result as CoreResult;
use crate::model_provider_info::ModelProviderInfo;
use crate::openai_models::model_family::ModelFamily;
use crate::openai_models::model_family::find_family_for_model;
use crate::openai_models::model_presets::builtin_model_presets;
#[derive(Debug)]
pub struct ModelsManager {
// todo(aibrahim) merge available_models and model family creation into one struct
pub available_models: RwLock<Vec<ModelPreset>>,
pub remote_models: RwLock<Vec<ModelInfo>>,
pub etag: String,
pub auth_mode: Option<AuthMode>,
pub auth_manager: Arc<AuthManager>,
}
impl ModelsManager {
pub fn new(auth_mode: Option<AuthMode>) -> Self {
pub fn new(auth_manager: Arc<AuthManager>) -> Self {
Self {
available_models: RwLock::new(builtin_model_presets(auth_mode)),
available_models: RwLock::new(builtin_model_presets(auth_manager.get_auth_mode())),
remote_models: RwLock::new(Vec::new()),
etag: String::new(),
auth_mode,
auth_manager,
}
}
pub async fn refresh_available_models(&self) {
let models = builtin_model_presets(self.auth_mode);
*self.available_models.write().await = models;
// do not use this function yet. It's work in progress.
pub async fn refresh_available_models(
&self,
provider: &ModelProviderInfo,
) -> CoreResult<Vec<ModelInfo>> {
let auth = self.auth_manager.auth();
let api_provider = provider.to_api_provider(auth.as_ref().map(|auth| auth.mode))?;
let api_auth = auth_provider_from_auth(auth.clone(), provider).await?;
let transport = ReqwestTransport::new(build_reqwest_client());
let client = ModelsClient::new(transport, api_provider, api_auth);
let response = client
.list_models(env!("CARGO_PKG_VERSION"), HeaderMap::new())
.await
.map_err(map_api_error)?;
let models = response.models;
*self.remote_models.write().await = models.clone();
{
let mut available_models_guard = self.available_models.write().await;
*available_models_guard = self.build_available_models().await;
}
Ok(models)
}
pub fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily {
find_family_for_model(model).with_config_overrides(config)
}
async fn build_available_models(&self) -> Vec<ModelPreset> {
let mut available_models = self.remote_models.read().await.clone();
available_models.sort_by(|a, b| b.priority.cmp(&a.priority));
let mut model_presets: Vec<ModelPreset> =
available_models.into_iter().map(Into::into).collect();
if let Some(default) = model_presets.first_mut() {
default.is_default = true;
}
model_presets
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CodexAuth;
use crate::model_provider_info::WireApi;
use codex_protocol::openai_models::ModelsResponse;
use serde_json::json;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
fn remote_model(slug: &str, display: &str, priority: i32) -> ModelInfo {
serde_json::from_value(json!({
"slug": slug,
"display_name": display,
"description": format!("{display} desc"),
"default_reasoning_level": "medium",
"supported_reasoning_levels": ["low", "medium"],
"shell_type": "shell_command",
"visibility": "list",
"minimal_client_version": [0, 1, 0],
"supported_in_api": true,
"priority": priority
}))
.expect("valid model")
}
fn provider_for(base_url: String) -> ModelProviderInfo {
ModelProviderInfo {
name: "mock".into(),
base_url: Some(base_url),
env_key: None,
env_key_instructions: None,
experimental_bearer_token: None,
wire_api: WireApi::Responses,
query_params: None,
http_headers: None,
env_http_headers: None,
request_max_retries: Some(0),
stream_max_retries: Some(0),
stream_idle_timeout_ms: Some(5_000),
requires_openai_auth: false,
}
}
#[tokio::test]
async fn refresh_available_models_sorts_and_marks_default() {
let server = MockServer::start().await;
let remote_models = vec![
remote_model("priority-low", "Low", 1),
remote_model("priority-high", "High", 10),
];
let response = ModelsResponse {
models: remote_models.clone(),
};
Mock::given(method("GET"))
.and(path("/models"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("content-type", "application/json")
.set_body_json(&response),
)
.expect(1)
.mount(&server)
.await;
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let manager = ModelsManager::new(auth_manager);
let provider = provider_for(server.uri());
let returned = manager
.refresh_available_models(&provider)
.await
.expect("refresh succeeds");
assert_eq!(returned, remote_models);
let cached_remote = manager.remote_models.read().await.clone();
assert_eq!(cached_remote, remote_models);
let available = manager.available_models.read().await.clone();
assert_eq!(available.len(), 2);
assert_eq!(available[0].model, "priority-high");
assert!(
available[0].is_default,
"highest priority should be default"
);
assert_eq!(available[1].model, "priority-low");
assert!(!available[1].is_default);
}
}
+2 -15
View File
@@ -8,6 +8,7 @@ use crate::tools::handlers::apply_patch::ApplyPatchToolType;
use crate::tools::handlers::apply_patch::create_apply_patch_freeform_tool;
use crate::tools::handlers::apply_patch::create_apply_patch_json_tool;
use crate::tools::registry::ToolRegistryBuilder;
use codex_protocol::openai_models::ConfigShellToolType;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
@@ -15,20 +16,6 @@ use serde_json::json;
use std::collections::BTreeMap;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConfigShellToolType {
Default,
Local,
UnifiedExec,
/// Do not include a shell tool by default. Useful when using Codex
/// with tools provided exclusively provided by MCP servers. Often used
/// with `--config base_instructions=CUSTOM_INSTRUCTIONS`
/// to customize agent behavior.
Disabled,
/// Takes a command as a single string to be run in the user's default shell.
ShellCommand,
}
#[derive(Debug, Clone)]
pub(crate) struct ToolsConfig {
pub shell_type: ConfigShellToolType,
@@ -58,7 +45,7 @@ impl ToolsConfig {
} else if features.enabled(Feature::UnifiedExec) {
ConfigShellToolType::UnifiedExec
} else {
model_family.shell_type.clone()
model_family.shell_type
};
let apply_patch_tool_type = match model_family.apply_patch_tool_type {
@@ -1,6 +1,8 @@
use std::sync::Arc;
use codex_app_server_protocol::AuthMode;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ContentItem;
use codex_core::LocalShellAction;
use codex_core::LocalShellExecAction;
@@ -71,7 +73,8 @@ async fn run_request(input: Vec<ResponseItem>) -> Value {
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let otel_event_manager = OtelEventManager::new(
conversation_id,
+6 -4
View File
@@ -1,9 +1,10 @@
use assert_matches::assert_matches;
use codex_core::AuthManager;
use codex_core::openai_models::models_manager::ModelsManager;
use std::sync::Arc;
use tracing_test::traced_test;
use codex_app_server_protocol::AuthMode;
use codex_core::CodexAuth;
use codex_core::ContentItem;
use codex_core::ModelClient;
use codex_core::ModelProviderInfo;
@@ -71,8 +72,9 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let auth_mode = AuthMode::ApiKey;
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let auth_mode = auth_manager.get_auth_mode();
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let otel_event_manager = OtelEventManager::new(
conversation_id,
@@ -80,7 +82,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
model_family.slug.as_str(),
None,
Some("test@test.com".to_string()),
Some(auth_mode),
auth_mode,
false,
"test".to_string(),
);
+9 -5
View File
@@ -1,6 +1,8 @@
use std::sync::Arc;
use codex_app_server_protocol::AuthMode;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ContentItem;
use codex_core::ModelClient;
use codex_core::ModelProviderInfo;
@@ -63,7 +65,8 @@ async fn responses_stream_includes_subagent_header_on_review() {
let conversation_id = ConversationId::new();
let auth_mode = AuthMode::ChatGPT;
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let otel_event_manager = OtelEventManager::new(
conversation_id,
@@ -154,7 +157,8 @@ async fn responses_stream_includes_subagent_header_on_other() {
let conversation_id = ConversationId::new();
let auth_mode = AuthMode::ChatGPT;
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let otel_event_manager = OtelEventManager::new(
@@ -246,8 +250,8 @@ async fn responses_respects_model_family_overrides_from_config() {
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let auth_mode = AuthMode::ChatGPT;
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
let model_family = models_manager.construct_model_family(&config.model, &config);
let otel_event_manager = OtelEventManager::new(
conversation_id,
@@ -255,7 +259,7 @@ async fn responses_respects_model_family_overrides_from_config() {
model_family.slug.as_str(),
None,
Some("test@test.com".to_string()),
Some(auth_mode),
auth_manager.get_auth_mode(),
false,
"test".to_string(),
);
+4 -4
View File
@@ -1,4 +1,4 @@
use codex_app_server_protocol::AuthMode;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ContentItem;
use codex_core::ConversationManager;
@@ -1017,8 +1017,8 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let auth_mode = AuthMode::ChatGPT;
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
let model_family = models_manager.construct_model_family(&config.model, &config);
let otel_event_manager = OtelEventManager::new(
conversation_id,
@@ -1026,7 +1026,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
model_family.slug.as_str(),
None,
Some("test@test.com".to_string()),
Some(AuthMode::ChatGPT),
auth_manager.get_auth_mode(),
false,
"test".to_string(),
);
+41 -20
View File
@@ -86,28 +86,24 @@ pub enum ModelVisibility {
None,
}
/// Reasoning support level reported by the backend.
#[derive(
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, TS, JsonSchema, EnumIter, Display,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ReasoningLevel {
None,
Minimal,
Low,
Medium,
High,
XHigh,
}
/// Shell execution capability for a model.
#[derive(
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, TS, JsonSchema, EnumIter, Display,
Debug,
Serialize,
Deserialize,
Clone,
Copy,
PartialEq,
Eq,
TS,
JsonSchema,
EnumIter,
Display,
Hash,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum ShellType {
pub enum ConfigShellToolType {
Default,
Local,
UnifiedExec,
@@ -126,9 +122,9 @@ pub struct ModelInfo {
pub display_name: String,
#[serde(default)]
pub description: Option<String>,
pub default_reasoning_level: ReasoningLevel,
pub supported_reasoning_levels: Vec<ReasoningLevel>,
pub shell_type: ShellType,
pub default_reasoning_level: ReasoningEffort,
pub supported_reasoning_levels: Vec<ReasoningEffort>,
pub shell_type: ConfigShellToolType,
#[serde(default = "default_visibility")]
pub visibility: ModelVisibility,
pub minimal_client_version: ClientVersion,
@@ -147,3 +143,28 @@ pub struct ModelsResponse {
fn default_visibility() -> ModelVisibility {
ModelVisibility::None
}
// convert ModelInfo to ModelPreset
impl From<ModelInfo> for ModelPreset {
fn from(info: ModelInfo) -> Self {
ModelPreset {
id: info.slug.clone(),
model: info.slug,
display_name: info.display_name,
description: info.description.unwrap_or_default(),
default_reasoning_effort: info.default_reasoning_level,
supported_reasoning_efforts: info
.supported_reasoning_levels
.into_iter()
.map(|level| ReasoningEffortPreset {
effort: level,
// todo: add description for each reasoning effort
description: level.to_string(),
})
.collect(),
is_default: false, // default is the highest priority available model
upgrade: None, // no upgrade available (todo: think about it)
show_in_picker: info.visibility == ModelVisibility::List,
}
}
}
+2 -2
View File
@@ -393,7 +393,7 @@ fn make_chatwidget_manual() -> (
active_cell: None,
config: cfg.clone(),
auth_manager: auth_manager.clone(),
models_manager: Arc::new(ModelsManager::new(auth_manager.get_auth_mode())),
models_manager: Arc::new(ModelsManager::new(auth_manager)),
session_header: SessionHeader::new(cfg.model),
initial_user_message: None,
token_info: None,
@@ -431,7 +431,7 @@ fn make_chatwidget_manual() -> (
fn set_chatgpt_auth(chat: &mut ChatWidget) {
chat.auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.get_auth_mode()));
chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.clone()));
}
pub(crate) fn make_chatwidget_manual_with_sender() -> (
+20 -7
View File
@@ -1513,6 +1513,8 @@ mod tests {
use crate::exec_cell::CommandOutput;
use crate::exec_cell::ExecCall;
use crate::exec_cell::ExecCell;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConfigToml;
@@ -1520,7 +1522,6 @@ mod tests {
use codex_core::config::types::McpServerTransportConfig;
use codex_core::openai_models::models_manager::ModelsManager;
use codex_core::protocol::McpAuthStatus;
use codex_login::AuthMode;
use codex_protocol::parse_command::ParsedCommand;
use dirs::home_dir;
use pretty_assertions::assert_eq;
@@ -2325,7 +2326,9 @@ mod tests {
#[test]
fn reasoning_summary_block() {
let config = test_config();
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let cell = new_reasoning_summary_block(
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
@@ -2342,7 +2345,9 @@ mod tests {
#[test]
fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() {
let config = test_config();
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let cell =
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &model_family);
@@ -2357,7 +2362,9 @@ mod tests {
config.model = "gpt-3.5-turbo".to_string();
config.model_supports_reasoning_summaries = Some(true);
config.model_reasoning_summary_format = Some(ReasoningSummaryFormat::Experimental);
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
assert_eq!(
@@ -2377,7 +2384,9 @@ mod tests {
#[test]
fn reasoning_summary_block_falls_back_when_header_is_missing() {
let config = test_config();
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let cell = new_reasoning_summary_block(
"**High level reasoning without closing".to_string(),
@@ -2391,7 +2400,9 @@ mod tests {
#[test]
fn reasoning_summary_block_falls_back_when_summary_is_missing() {
let config = test_config();
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let cell = new_reasoning_summary_block(
"**High level reasoning without closing**".to_string(),
@@ -2413,7 +2424,9 @@ mod tests {
#[test]
fn reasoning_summary_block_splits_header_and_summary_when_present() {
let config = test_config();
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let models_manager = Arc::new(ModelsManager::new(auth_manager));
let model_family = models_manager.construct_model_family(&config.model, &config);
let cell = new_reasoning_summary_block(
"**High level plan**\n\nWe should fix the bug next.".to_string(),