mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
remove model_family from `config (#7571)
- Remove `model_family` from `config` - Make sure to still override config elements related to `model_family` like supporting reasoning
This commit is contained in:
committed by
GitHub
Unverified
parent
ce0b38c056
commit
9b2055586d
@@ -12,15 +12,14 @@ pub fn create_config_summary_entries(config: &Config) -> Vec<(&'static str, Stri
|
||||
("approval", config.approval_policy.to_string()),
|
||||
("sandbox", summarize_sandbox_policy(&config.sandbox_policy)),
|
||||
];
|
||||
if config.model_provider.wire_api == WireApi::Responses
|
||||
&& config.model_family.supports_reasoning_summaries
|
||||
{
|
||||
if config.model_provider.wire_api == WireApi::Responses {
|
||||
let reasoning_effort = config
|
||||
.model_reasoning_effort
|
||||
.or(config.model_family.default_reasoning_effort)
|
||||
.map(|effort| effort.to_string())
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
entries.push(("reasoning effort", reasoning_effort));
|
||||
.map(|effort| effort.to_string());
|
||||
entries.push((
|
||||
"reasoning effort",
|
||||
reasoning_effort.unwrap_or_else(|| "none".to_string()),
|
||||
));
|
||||
entries.push((
|
||||
"reasoning summaries",
|
||||
config.model_reasoning_summary.to_string(),
|
||||
|
||||
+20
-19
@@ -57,6 +57,7 @@ use crate::tools::spec::create_tools_json_for_responses_api;
|
||||
pub struct ModelClient {
|
||||
config: Arc<Config>,
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
model_family: ModelFamily,
|
||||
otel_event_manager: OtelEventManager,
|
||||
provider: ModelProviderInfo,
|
||||
conversation_id: ConversationId,
|
||||
@@ -70,6 +71,7 @@ impl ModelClient {
|
||||
pub fn new(
|
||||
config: Arc<Config>,
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
model_family: ModelFamily,
|
||||
otel_event_manager: OtelEventManager,
|
||||
provider: ModelProviderInfo,
|
||||
effort: Option<ReasoningEffortConfig>,
|
||||
@@ -80,6 +82,7 @@ impl ModelClient {
|
||||
Self {
|
||||
config,
|
||||
auth_manager,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
conversation_id,
|
||||
@@ -90,16 +93,18 @@ impl ModelClient {
|
||||
}
|
||||
|
||||
pub fn get_model_context_window(&self) -> Option<i64> {
|
||||
let pct = self.config.model_family.effective_context_window_percent;
|
||||
let model_family = self.get_model_family();
|
||||
let effective_context_window_percent = model_family.effective_context_window_percent;
|
||||
self.config
|
||||
.model_context_window
|
||||
.or_else(|| get_model_info(&self.config.model_family).map(|info| info.context_window))
|
||||
.map(|w| w.saturating_mul(pct) / 100)
|
||||
.or_else(|| get_model_info(&model_family).map(|info| info.context_window))
|
||||
.map(|w| w.saturating_mul(effective_context_window_percent) / 100)
|
||||
}
|
||||
|
||||
pub fn get_auto_compact_token_limit(&self) -> Option<i64> {
|
||||
let model_family = self.get_model_family();
|
||||
self.config.model_auto_compact_token_limit.or_else(|| {
|
||||
get_model_info(&self.config.model_family).and_then(|info| info.auto_compact_token_limit)
|
||||
get_model_info(&model_family).and_then(|info| info.auto_compact_token_limit)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -149,9 +154,8 @@ impl ModelClient {
|
||||
}
|
||||
|
||||
let auth_manager = self.auth_manager.clone();
|
||||
let instructions = prompt
|
||||
.get_full_instructions(&self.config.model_family)
|
||||
.into_owned();
|
||||
let model_family = self.get_model_family();
|
||||
let instructions = prompt.get_full_instructions(&model_family).into_owned();
|
||||
let tools_json = create_tools_json_for_chat_completions_api(&prompt.tools)?;
|
||||
let api_prompt = build_api_prompt(prompt, instructions, tools_json);
|
||||
let conversation_id = self.conversation_id.to_string();
|
||||
@@ -204,16 +208,13 @@ impl ModelClient {
|
||||
}
|
||||
|
||||
let auth_manager = self.auth_manager.clone();
|
||||
let instructions = prompt
|
||||
.get_full_instructions(&self.config.model_family)
|
||||
.into_owned();
|
||||
let model_family = self.get_model_family();
|
||||
let instructions = prompt.get_full_instructions(&model_family).into_owned();
|
||||
let tools_json: Vec<Value> = create_tools_json_for_responses_api(&prompt.tools)?;
|
||||
|
||||
let reasoning = if self.config.model_family.supports_reasoning_summaries {
|
||||
let reasoning = if model_family.supports_reasoning_summaries {
|
||||
Some(Reasoning {
|
||||
effort: self
|
||||
.effort
|
||||
.or(self.config.model_family.default_reasoning_effort),
|
||||
effort: self.effort.or(model_family.default_reasoning_effort),
|
||||
summary: Some(self.summary),
|
||||
})
|
||||
} else {
|
||||
@@ -226,15 +227,15 @@ impl ModelClient {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let verbosity = if self.config.model_family.support_verbosity {
|
||||
let verbosity = if model_family.support_verbosity {
|
||||
self.config
|
||||
.model_verbosity
|
||||
.or(self.config.model_family.default_verbosity)
|
||||
.or(model_family.default_verbosity)
|
||||
} else {
|
||||
if self.config.model_verbosity.is_some() {
|
||||
warn!(
|
||||
"model_verbosity is set but ignored as the model does not support verbosity: {}",
|
||||
self.config.model_family.family
|
||||
model_family.family
|
||||
);
|
||||
}
|
||||
None
|
||||
@@ -305,7 +306,7 @@ impl ModelClient {
|
||||
|
||||
/// Returns the currently configured model family.
|
||||
pub fn get_model_family(&self) -> ModelFamily {
|
||||
self.config.model_family.clone()
|
||||
self.model_family.clone()
|
||||
}
|
||||
|
||||
/// Returns the current reasoning effort setting.
|
||||
@@ -342,7 +343,7 @@ impl ModelClient {
|
||||
.with_telemetry(Some(request_telemetry));
|
||||
|
||||
let instructions = prompt
|
||||
.get_full_instructions(&self.config.model_family)
|
||||
.get_full_instructions(&self.get_model_family())
|
||||
.into_owned();
|
||||
let payload = ApiCompactionInput {
|
||||
model: &self.config.model,
|
||||
|
||||
+36
-20
@@ -398,7 +398,7 @@ pub(crate) struct SessionSettingsUpdate {
|
||||
impl Session {
|
||||
fn make_turn_context(
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
models_manager: &ModelsManager,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
otel_event_manager: &OtelEventManager,
|
||||
provider: ModelProviderInfo,
|
||||
session_configuration: &SessionConfiguration,
|
||||
@@ -407,13 +407,13 @@ impl Session {
|
||||
) -> TurnContext {
|
||||
let config = session_configuration.original_config_do_not_use.clone();
|
||||
let features = &config.features;
|
||||
let model_family = models_manager.construct_model_family(&session_configuration.model);
|
||||
let mut per_turn_config = (*config).clone();
|
||||
per_turn_config.model = session_configuration.model.clone();
|
||||
per_turn_config.model_family = model_family.clone();
|
||||
per_turn_config.model_reasoning_effort = session_configuration.model_reasoning_effort;
|
||||
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
|
||||
per_turn_config.features = features.clone();
|
||||
let model_family =
|
||||
models_manager.construct_model_family(&per_turn_config.model, &per_turn_config);
|
||||
if let Some(model_info) = get_model_info(&model_family) {
|
||||
per_turn_config.model_context_window = Some(model_info.context_window);
|
||||
}
|
||||
@@ -426,6 +426,7 @@ impl Session {
|
||||
let client = ModelClient::new(
|
||||
Arc::new(per_turn_config.clone()),
|
||||
auth_manager,
|
||||
model_family.clone(),
|
||||
otel_event_manager,
|
||||
provider,
|
||||
session_configuration.model_reasoning_effort,
|
||||
@@ -455,7 +456,10 @@ impl Session {
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
tool_call_gate: Arc::new(ReadinessFlag::new()),
|
||||
exec_policy: session_configuration.exec_policy.clone(),
|
||||
truncation_policy: TruncationPolicy::new(&per_turn_config),
|
||||
truncation_policy: TruncationPolicy::new(
|
||||
&per_turn_config,
|
||||
model_family.truncation_policy,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,10 +543,12 @@ impl Session {
|
||||
});
|
||||
}
|
||||
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
// todo(aibrahim): why are we passing model here while it can change?
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
auth_manager.auth().and_then(|a| a.get_account_id()),
|
||||
auth_manager.auth().and_then(|a| a.get_account_email()),
|
||||
auth_manager.auth().map(|a| a.mode),
|
||||
@@ -762,7 +768,7 @@ impl Session {
|
||||
|
||||
let mut turn_context: TurnContext = Self::make_turn_context(
|
||||
Some(Arc::clone(&self.services.auth_manager)),
|
||||
&self.services.models_manager,
|
||||
Arc::clone(&self.services.models_manager),
|
||||
&self.services.otel_event_manager,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
@@ -862,6 +868,7 @@ impl Session {
|
||||
auth_manager,
|
||||
&otel,
|
||||
self.conversation_id,
|
||||
self.services.models_manager.clone(),
|
||||
turn_context.client.get_session_source(),
|
||||
call_id,
|
||||
command,
|
||||
@@ -1891,7 +1898,10 @@ async fn spawn_review_thread(
|
||||
resolved: crate::review_prompts::ResolvedReviewRequest,
|
||||
) {
|
||||
let model = config.review_model.clone();
|
||||
let review_model_family = sess.services.models_manager.construct_model_family(&model);
|
||||
let review_model_family = sess
|
||||
.services
|
||||
.models_manager
|
||||
.construct_model_family(&model, &config);
|
||||
// For reviews, disable web_search and view_image regardless of global settings.
|
||||
let mut review_features = sess.features.clone();
|
||||
review_features
|
||||
@@ -1911,7 +1921,6 @@ async fn spawn_review_thread(
|
||||
// Build per‑turn client with the requested model/family.
|
||||
let mut per_turn_config = (*config).clone();
|
||||
per_turn_config.model = model.clone();
|
||||
per_turn_config.model_family = model_family.clone();
|
||||
per_turn_config.model_reasoning_effort = Some(ReasoningEffortConfig::Low);
|
||||
per_turn_config.model_reasoning_summary = ReasoningSummaryConfig::Detailed;
|
||||
per_turn_config.features = review_features.clone();
|
||||
@@ -1924,13 +1933,14 @@ async fn spawn_review_thread(
|
||||
.get_otel_event_manager()
|
||||
.with_model(
|
||||
per_turn_config.model.as_str(),
|
||||
per_turn_config.model_family.slug.as_str(),
|
||||
review_model_family.slug.as_str(),
|
||||
);
|
||||
|
||||
let per_turn_config = Arc::new(per_turn_config);
|
||||
let client = ModelClient::new(
|
||||
per_turn_config.clone(),
|
||||
auth_manager,
|
||||
model_family.clone(),
|
||||
otel_event_manager,
|
||||
provider,
|
||||
per_turn_config.model_reasoning_effort,
|
||||
@@ -1955,7 +1965,7 @@ async fn spawn_review_thread(
|
||||
codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(),
|
||||
tool_call_gate: Arc::new(ReadinessFlag::new()),
|
||||
exec_policy: parent_turn_context.exec_policy.clone(),
|
||||
truncation_policy: TruncationPolicy::new(&per_turn_config),
|
||||
truncation_policy: TruncationPolicy::new(&per_turn_config, model_family.truncation_policy),
|
||||
};
|
||||
|
||||
// Seed the child task with the review prompt as the initial user message.
|
||||
@@ -2149,10 +2159,7 @@ async fn run_turn(
|
||||
let mut base_instructions = turn_context.base_instructions.clone();
|
||||
if parallel_tool_calls {
|
||||
static INSTRUCTIONS: &str = include_str!("../templates/parallel/instructions.md");
|
||||
let family = sess
|
||||
.services
|
||||
.models_manager
|
||||
.construct_model_family(&sess.state.lock().await.session_configuration.model);
|
||||
let family = turn_context.client.get_model_family();
|
||||
let mut new_instructions = base_instructions.unwrap_or(family.base_instructions);
|
||||
new_instructions.push_str(INSTRUCTIONS);
|
||||
base_instructions = Some(new_instructions);
|
||||
@@ -2787,11 +2794,18 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
fn otel_event_manager(conversation_id: ConversationId, config: &Config) -> OtelEventManager {
|
||||
fn otel_event_manager(
|
||||
conversation_id: ConversationId,
|
||||
config: &Config,
|
||||
models_manager: &ModelsManager,
|
||||
) -> OtelEventManager {
|
||||
OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
models_manager
|
||||
.construct_model_family(&config.model, config)
|
||||
.slug
|
||||
.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
@@ -2811,13 +2825,14 @@ mod tests {
|
||||
.expect("load default test config");
|
||||
let config = Arc::new(config);
|
||||
let conversation_id = ConversationId::default();
|
||||
let otel_event_manager = otel_event_manager(conversation_id, config.as_ref());
|
||||
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 otel_event_manager =
|
||||
otel_event_manager(conversation_id, config.as_ref(), &models_manager);
|
||||
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
@@ -2854,7 +2869,7 @@ mod tests {
|
||||
|
||||
let turn_context = Session::make_turn_context(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
&models_manager,
|
||||
models_manager,
|
||||
&otel_event_manager,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
@@ -2892,13 +2907,14 @@ mod tests {
|
||||
.expect("load default test config");
|
||||
let config = Arc::new(config);
|
||||
let conversation_id = ConversationId::default();
|
||||
let otel_event_manager = otel_event_manager(conversation_id, config.as_ref());
|
||||
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 otel_event_manager =
|
||||
otel_event_manager(conversation_id, config.as_ref(), &models_manager);
|
||||
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
@@ -2935,7 +2951,7 @@ mod tests {
|
||||
|
||||
let turn_context = Arc::new(Session::make_turn_context(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
&models_manager,
|
||||
models_manager,
|
||||
&otel_event_manager,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
|
||||
@@ -27,7 +27,6 @@ use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use crate::model_provider_info::built_in_model_providers;
|
||||
use crate::openai_model_info::get_model_info;
|
||||
use crate::openai_models::model_family::ModelFamily;
|
||||
use crate::openai_models::model_family::find_family_for_model;
|
||||
use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
||||
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
|
||||
@@ -80,9 +79,6 @@ pub struct Config {
|
||||
/// Model used specifically for review sessions. Defaults to "gpt-5.1-codex-max".
|
||||
pub review_model: String,
|
||||
|
||||
// todo(aibrahim): remove this field
|
||||
pub model_family: ModelFamily,
|
||||
|
||||
/// Size of the context window for the model, in tokens.
|
||||
pub model_context_window: Option<i64>,
|
||||
|
||||
@@ -195,6 +191,7 @@ pub struct Config {
|
||||
/// Additional filenames to try when looking for project-level docs.
|
||||
pub project_doc_fallback_filenames: Vec<String>,
|
||||
|
||||
// todo(aibrahim): this should be used in the override model family
|
||||
/// Token budget applied when storing tool/function outputs in the context manager.
|
||||
pub tool_output_token_limit: Option<usize>,
|
||||
|
||||
@@ -225,6 +222,12 @@ pub struct Config {
|
||||
/// request using the Responses API.
|
||||
pub model_reasoning_summary: ReasoningSummary,
|
||||
|
||||
/// Optional override to force-enable reasoning summaries for the configured model.
|
||||
pub model_supports_reasoning_summaries: Option<bool>,
|
||||
|
||||
/// Optional override to force reasoning summary format for the configured model.
|
||||
pub model_reasoning_summary_format: Option<ReasoningSummaryFormat>,
|
||||
|
||||
/// Optional verbosity control for GPT-5 models (Responses API `text.verbosity`).
|
||||
pub model_verbosity: Option<Verbosity>,
|
||||
|
||||
@@ -1108,14 +1111,7 @@ impl Config {
|
||||
.or(cfg.model)
|
||||
.unwrap_or_else(default_model);
|
||||
|
||||
let mut model_family = find_family_for_model(&model);
|
||||
|
||||
if let Some(supports_reasoning_summaries) = cfg.model_supports_reasoning_summaries {
|
||||
model_family.supports_reasoning_summaries = supports_reasoning_summaries;
|
||||
}
|
||||
if let Some(model_reasoning_summary_format) = cfg.model_reasoning_summary_format {
|
||||
model_family.reasoning_summary_format = model_reasoning_summary_format;
|
||||
}
|
||||
let model_family = find_family_for_model(&model);
|
||||
|
||||
let openai_model_info = get_model_info(&model_family);
|
||||
let model_context_window = cfg
|
||||
@@ -1172,7 +1168,6 @@ impl Config {
|
||||
let config = Self {
|
||||
model,
|
||||
review_model,
|
||||
model_family,
|
||||
model_context_window,
|
||||
model_auto_compact_token_limit,
|
||||
model_provider_id,
|
||||
@@ -1228,6 +1223,8 @@ impl Config {
|
||||
.model_reasoning_summary
|
||||
.or(cfg.model_reasoning_summary)
|
||||
.unwrap_or_default(),
|
||||
model_supports_reasoning_summaries: cfg.model_supports_reasoning_summaries,
|
||||
model_reasoning_summary_format: cfg.model_reasoning_summary_format.clone(),
|
||||
model_verbosity: config_profile.model_verbosity.or(cfg.model_verbosity),
|
||||
chatgpt_base_url: config_profile
|
||||
.chatgpt_base_url
|
||||
@@ -2953,7 +2950,6 @@ model_verbosity = "high"
|
||||
Config {
|
||||
model: "o3".to_string(),
|
||||
review_model: OPENAI_DEFAULT_REVIEW_MODEL.to_string(),
|
||||
model_family: find_family_for_model("o3"),
|
||||
model_context_window: Some(200_000),
|
||||
model_auto_compact_token_limit: Some(180_000),
|
||||
model_provider_id: "openai".to_string(),
|
||||
@@ -2981,6 +2977,8 @@ model_verbosity = "high"
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: Some(ReasoningEffort::High),
|
||||
model_reasoning_summary: ReasoningSummary::Detailed,
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_reasoning_summary_format: None,
|
||||
model_verbosity: None,
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
base_instructions: None,
|
||||
@@ -3027,7 +3025,6 @@ model_verbosity = "high"
|
||||
let expected_gpt3_profile_config = Config {
|
||||
model: "gpt-3.5-turbo".to_string(),
|
||||
review_model: OPENAI_DEFAULT_REVIEW_MODEL.to_string(),
|
||||
model_family: find_family_for_model("gpt-3.5-turbo"),
|
||||
model_context_window: Some(16_385),
|
||||
model_auto_compact_token_limit: Some(14_746),
|
||||
model_provider_id: "openai-chat-completions".to_string(),
|
||||
@@ -3055,6 +3052,8 @@ model_verbosity = "high"
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: None,
|
||||
model_reasoning_summary: ReasoningSummary::default(),
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_reasoning_summary_format: None,
|
||||
model_verbosity: None,
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
base_instructions: None,
|
||||
@@ -3116,7 +3115,6 @@ model_verbosity = "high"
|
||||
let expected_zdr_profile_config = Config {
|
||||
model: "o3".to_string(),
|
||||
review_model: OPENAI_DEFAULT_REVIEW_MODEL.to_string(),
|
||||
model_family: find_family_for_model("o3"),
|
||||
model_context_window: Some(200_000),
|
||||
model_auto_compact_token_limit: Some(180_000),
|
||||
model_provider_id: "openai".to_string(),
|
||||
@@ -3144,6 +3142,8 @@ model_verbosity = "high"
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: None,
|
||||
model_reasoning_summary: ReasoningSummary::default(),
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_reasoning_summary_format: None,
|
||||
model_verbosity: None,
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
base_instructions: None,
|
||||
@@ -3191,7 +3191,6 @@ model_verbosity = "high"
|
||||
let expected_gpt5_profile_config = Config {
|
||||
model: "gpt-5.1".to_string(),
|
||||
review_model: OPENAI_DEFAULT_REVIEW_MODEL.to_string(),
|
||||
model_family: find_family_for_model("gpt-5.1"),
|
||||
model_context_window: Some(272_000),
|
||||
model_auto_compact_token_limit: Some(244_800),
|
||||
model_provider_id: "openai".to_string(),
|
||||
@@ -3219,6 +3218,8 @@ model_verbosity = "high"
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: Some(ReasoningEffort::High),
|
||||
model_reasoning_summary: ReasoningSummary::Detailed,
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_reasoning_summary_format: None,
|
||||
model_verbosity: Some(Verbosity::High),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
base_instructions: None,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
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;
|
||||
@@ -72,6 +73,18 @@ pub struct ModelFamily {
|
||||
pub truncation_policy: TruncationPolicy,
|
||||
}
|
||||
|
||||
impl ModelFamily {
|
||||
pub fn with_config_overrides(mut self, config: &Config) -> Self {
|
||||
if let Some(supports_reasoning_summaries) = config.model_supports_reasoning_summaries {
|
||||
self.supports_reasoning_summaries = supports_reasoning_summaries;
|
||||
}
|
||||
if let Some(reasoning_summary_format) = config.model_reasoning_summary_format.as_ref() {
|
||||
self.reasoning_summary_format = reasoning_summary_format.clone();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! model_family {
|
||||
(
|
||||
$slug:expr, $family:expr $(, $key:ident : $value:expr )* $(,)?
|
||||
|
||||
@@ -2,10 +2,12 @@ use codex_app_server_protocol::AuthMode;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::config::Config;
|
||||
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 {
|
||||
pub available_models: RwLock<Vec<ModelPreset>>,
|
||||
pub etag: String,
|
||||
@@ -26,7 +28,7 @@ impl ModelsManager {
|
||||
*self.available_models.write().await = models;
|
||||
}
|
||||
|
||||
pub fn construct_model_family(&self, model: &str) -> ModelFamily {
|
||||
find_family_for_model(model)
|
||||
pub fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily {
|
||||
find_family_for_model(model).with_config_overrides(config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::client::ModelClient;
|
||||
use crate::client_common::Prompt;
|
||||
use crate::client_common::ResponseEvent;
|
||||
use crate::config::Config;
|
||||
use crate::openai_models::models_manager::ModelsManager;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use askama::Template;
|
||||
use codex_otel::otel_event_manager::OtelEventManager;
|
||||
@@ -46,6 +47,7 @@ pub(crate) async fn assess_command(
|
||||
auth_manager: Arc<AuthManager>,
|
||||
parent_otel: &OtelEventManager,
|
||||
conversation_id: ConversationId,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
session_source: SessionSource,
|
||||
call_id: &str,
|
||||
command: &[String],
|
||||
@@ -124,12 +126,14 @@ pub(crate) async fn assess_command(
|
||||
output_schema: Some(sandbox_assessment_schema()),
|
||||
};
|
||||
|
||||
let child_otel =
|
||||
parent_otel.with_model(config.model.as_str(), config.model_family.slug.as_str());
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
|
||||
let child_otel = parent_otel.with_model(config.model.as_str(), model_family.slug.as_str());
|
||||
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
Some(auth_manager),
|
||||
model_family,
|
||||
child_otel,
|
||||
provider,
|
||||
Some(SANDBOX_ASSESSMENT_REASONING_EFFORT),
|
||||
|
||||
@@ -26,10 +26,10 @@ impl TruncationPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(config: &Config) -> Self {
|
||||
pub fn new(config: &Config, truncation_policy: TruncationPolicy) -> Self {
|
||||
let config_token_limit = config.tool_output_token_limit;
|
||||
|
||||
match config.model_family.truncation_policy {
|
||||
match truncation_policy {
|
||||
TruncationPolicy::Bytes(family_bytes) => {
|
||||
if let Some(token_limit) = config_token_limit {
|
||||
Self::Bytes(approx_bytes_for_tokens(token_limit))
|
||||
|
||||
@@ -10,6 +10,7 @@ use codex_core::ModelProviderInfo;
|
||||
use codex_core::Prompt;
|
||||
use codex_core::ResponseItem;
|
||||
use codex_core::WireApi;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use codex_otel::otel_event_manager::OtelEventManager;
|
||||
use codex_protocol::ConversationId;
|
||||
use codex_protocol::models::ReasoningItemContent;
|
||||
@@ -70,14 +71,15 @@ 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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
Some(AuthMode::ApiKey),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
@@ -85,6 +87,7 @@ async fn run_request(input: Vec<ResponseItem>) -> Value {
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
None,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
effort,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use assert_matches::assert_matches;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use std::sync::Arc;
|
||||
use tracing_test::traced_test;
|
||||
|
||||
@@ -70,14 +71,16 @@ 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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
Some(auth_mode),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
@@ -85,6 +88,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
None,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
effort,
|
||||
|
||||
@@ -118,6 +118,7 @@ impl TestCodexBuilder {
|
||||
config,
|
||||
codex: new_conversation.conversation,
|
||||
session_configured: new_conversation.session_configured,
|
||||
conversation_manager: Arc::new(conversation_manager),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -160,6 +161,7 @@ pub struct TestCodex {
|
||||
pub codex: Arc<CodexConversation>,
|
||||
pub session_configured: SessionConfiguredEvent,
|
||||
pub config: Config,
|
||||
pub conversation_manager: Arc<ConversationManager>,
|
||||
}
|
||||
|
||||
impl TestCodex {
|
||||
|
||||
@@ -8,8 +8,11 @@ use codex_core::Prompt;
|
||||
use codex_core::ResponseEvent;
|
||||
use codex_core::ResponseItem;
|
||||
use codex_core::WireApi;
|
||||
use codex_core::config::types::ReasoningSummaryFormat;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use codex_otel::otel_event_manager::OtelEventManager;
|
||||
use codex_protocol::ConversationId;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::responses;
|
||||
@@ -59,14 +62,16 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
Some(auth_mode),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
@@ -74,6 +79,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
None,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
effort,
|
||||
@@ -147,14 +153,17 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
Some(auth_mode),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
@@ -162,6 +171,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
None,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
effort,
|
||||
@@ -194,3 +204,109 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
Some("my-task")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn responses_respects_model_family_overrides_from_config() {
|
||||
core_test_support::skip_if_no_network!();
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_body = responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]);
|
||||
|
||||
let request_recorder = responses::mount_sse_once(&server, response_body).await;
|
||||
|
||||
let provider = ModelProviderInfo {
|
||||
name: "mock".into(),
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
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,
|
||||
};
|
||||
|
||||
let codex_home = TempDir::new().expect("failed to create TempDir");
|
||||
let mut config = load_default_config_for_test(&codex_home);
|
||||
config.model = "gpt-3.5-turbo".to_string();
|
||||
config.model_provider_id = provider.name.clone();
|
||||
config.model_provider = provider.clone();
|
||||
config.model_supports_reasoning_summaries = Some(true);
|
||||
config.model_reasoning_summary_format = Some(ReasoningSummaryFormat::Experimental);
|
||||
config.model_reasoning_summary = ReasoningSummary::Detailed;
|
||||
let effort = config.model_reasoning_effort;
|
||||
let summary = config.model_reasoning_summary;
|
||||
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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(auth_mode),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
None,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
effort,
|
||||
summary,
|
||||
conversation_id,
|
||||
SessionSource::SubAgent(codex_protocol::protocol::SubAgentSource::Other(
|
||||
"override-check".to_string(),
|
||||
)),
|
||||
);
|
||||
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input = vec![ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".into(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "hello".into(),
|
||||
}],
|
||||
}];
|
||||
|
||||
let mut stream = client.stream(&prompt).await.expect("stream failed");
|
||||
while let Some(event) = stream.next().await {
|
||||
if matches!(event, Ok(ResponseEvent::Completed { .. })) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let request = request_recorder.single_request();
|
||||
let body = request.body_json();
|
||||
let reasoning = body
|
||||
.get("reasoning")
|
||||
.and_then(|value| value.as_object())
|
||||
.cloned();
|
||||
|
||||
assert!(
|
||||
reasoning.is_some(),
|
||||
"reasoning should be present when config enables summaries"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
reasoning
|
||||
.as_ref()
|
||||
.and_then(|value| value.get("summary"))
|
||||
.and_then(|value| value.as_str()),
|
||||
Some("detailed")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_core::built_in_model_providers;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::openai_models::model_family::find_family_for_model;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::Op;
|
||||
use codex_core::protocol::SessionSource;
|
||||
@@ -1017,11 +1017,13 @@ 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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
config.model_family.slug.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
@@ -1032,6 +1034,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
let client = ModelClient::new(
|
||||
Arc::clone(&config),
|
||||
None,
|
||||
model_family,
|
||||
otel_event_manager,
|
||||
provider,
|
||||
effort,
|
||||
@@ -1378,7 +1381,6 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.model = "gpt-5.1".to_string();
|
||||
config.model_family = find_family_for_model("gpt-5.1");
|
||||
config.model_context_window = Some(272_000);
|
||||
})
|
||||
.build(&server)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::openai_models::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG;
|
||||
use codex_core::protocol::EventMsg;
|
||||
@@ -73,7 +72,6 @@ async fn codex_mini_latest_tools() -> anyhow::Result<()> {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
config.features.disable(Feature::ApplyPatchFreeform);
|
||||
config.model = "codex-mini-latest".to_string();
|
||||
config.model_family = find_family_for_model("codex-mini-latest")
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
@@ -125,13 +123,22 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
||||
let req1 = mount_sse_once(&server, sse_completed("resp-1")).await;
|
||||
let req2 = mount_sse_once(&server, sse_completed("resp-2")).await;
|
||||
|
||||
let TestCodex { codex, config, .. } = test_codex()
|
||||
let TestCodex {
|
||||
codex,
|
||||
config,
|
||||
conversation_manager,
|
||||
..
|
||||
} = test_codex()
|
||||
.with_config(|config| {
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
let base_instructions = config.model_family.base_instructions.clone();
|
||||
let base_instructions = conversation_manager
|
||||
.get_models_manager()
|
||||
.construct_model_family(&config.model, &config)
|
||||
.base_instructions
|
||||
.clone();
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use anyhow::Result;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::openai_models::model_family::find_family_for_model;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use core_test_support::assert_regex_match;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
@@ -18,6 +15,7 @@ use core_test_support::responses::start_mock_server;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::test_codex::ApplyPatchModelOutput;
|
||||
use core_test_support::test_codex::ShellModelOutput;
|
||||
use core_test_support::test_codex::TestCodexBuilder;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use pretty_assertions::assert_eq;
|
||||
use regex_lite::Regex;
|
||||
@@ -41,19 +39,6 @@ const FIXTURE_JSON: &str = r#"{
|
||||
}
|
||||
"#;
|
||||
|
||||
fn configure_shell_command_model(output_type: ShellModelOutput, config: &mut Config) {
|
||||
if !matches!(output_type, ShellModelOutput::ShellCommand) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shell_command_family = find_family_for_model("test-gpt-5-codex");
|
||||
if config.model_family.shell_type == shell_command_family.shell_type {
|
||||
return;
|
||||
}
|
||||
config.model = shell_command_family.slug.clone();
|
||||
config.model_family = shell_command_family;
|
||||
}
|
||||
|
||||
fn shell_responses(
|
||||
call_id: &str,
|
||||
command: Vec<&str>,
|
||||
@@ -113,6 +98,24 @@ fn shell_responses(
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_shell_model(
|
||||
builder: TestCodexBuilder,
|
||||
output_type: ShellModelOutput,
|
||||
include_apply_patch_tool: bool,
|
||||
) -> TestCodexBuilder {
|
||||
let builder = match (output_type, include_apply_patch_tool) {
|
||||
(ShellModelOutput::ShellCommand, _) => builder.with_model("test-gpt-5-codex"),
|
||||
(ShellModelOutput::LocalShell, true) => builder.with_model("gpt-5.1-codex"),
|
||||
(ShellModelOutput::Shell, true) => builder.with_model("gpt-5.1-codex"),
|
||||
(ShellModelOutput::LocalShell, false) => builder.with_model("codex-mini-latest"),
|
||||
(ShellModelOutput::Shell, false) => builder.with_model("gpt-5"),
|
||||
};
|
||||
|
||||
builder.with_config(move |config| {
|
||||
config.include_apply_patch_tool = include_apply_patch_tool;
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[test_case(ShellModelOutput::Shell)]
|
||||
#[test_case(ShellModelOutput::LocalShell)]
|
||||
@@ -122,10 +125,7 @@ async fn shell_output_stays_json_without_freeform_apply_patch(
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_model("gpt-5").with_config(move |config| {
|
||||
config.features.disable(Feature::ApplyPatchFreeform);
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let mut builder = configure_shell_model(test_codex(), output_type, false);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let call_id = "shell-json";
|
||||
@@ -177,10 +177,7 @@ async fn shell_output_is_structured_with_freeform_apply_patch(
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.features.enable(Feature::ApplyPatchFreeform);
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let mut builder = configure_shell_model(test_codex(), output_type, true);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let call_id = "shell-structured";
|
||||
@@ -225,10 +222,7 @@ async fn shell_output_preserves_fixture_json_without_serialization(
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_model("gpt-5").with_config(move |config| {
|
||||
config.features.disable(Feature::ApplyPatchFreeform);
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let mut builder = configure_shell_model(test_codex(), output_type, false);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let fixture_path = test.cwd.path().join("fixture.json");
|
||||
@@ -292,10 +286,7 @@ async fn shell_output_structures_fixture_with_serialization(
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.features.enable(Feature::ApplyPatchFreeform);
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let mut builder = configure_shell_model(test_codex(), output_type, true);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let fixture_path = test.cwd.path().join("fixture.json");
|
||||
@@ -354,10 +345,7 @@ async fn shell_output_for_freeform_tool_records_duration(
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.include_apply_patch_tool = true;
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let mut builder = configure_shell_model(test_codex(), output_type, true);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
let call_id = "shell-structured";
|
||||
@@ -407,11 +395,9 @@ async fn shell_output_reserializes_truncated_content(output_type: ShellModelOutp
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex()
|
||||
.with_model("gpt-5.1-codex")
|
||||
.with_config(move |config| {
|
||||
let mut builder =
|
||||
configure_shell_model(test_codex(), output_type, true).with_config(move |config| {
|
||||
config.tool_output_token_limit = Some(200);
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
@@ -712,7 +698,6 @@ async fn shell_output_is_structured_for_nonzero_exit(output_type: ShellModelOutp
|
||||
.with_model("gpt-5.1-codex")
|
||||
.with_config(move |config| {
|
||||
config.include_apply_patch_tool = true;
|
||||
configure_shell_command_model(output_type, config);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
@@ -748,7 +733,7 @@ async fn shell_command_output_is_freeform() -> Result<()> {
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
configure_shell_command_model(ShellModelOutput::ShellCommand, config);
|
||||
config.include_apply_patch_tool = true;
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ use codex_core::ConversationManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::openai_models::model_family::find_family_for_model;
|
||||
use codex_core::openai_models::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
|
||||
use codex_core::openai_models::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
@@ -162,7 +161,6 @@ async fn handle_model_migration_prompt_if_needed(
|
||||
migration_config: migration_config_key.to_string(),
|
||||
});
|
||||
config.model = target_model.to_string();
|
||||
config.model_family = find_family_for_model(&target_model);
|
||||
|
||||
let mapped_effort = if let Some(reasoning_effort_mapping) = reasoning_effort_mapping
|
||||
&& let Some(reasoning_effort) = config.model_reasoning_effort
|
||||
@@ -680,9 +678,7 @@ impl App {
|
||||
}
|
||||
AppEvent::UpdateModel(model) => {
|
||||
self.chat_widget.set_model(&model);
|
||||
self.config.model = model.clone();
|
||||
let family = find_family_for_model(&model);
|
||||
self.config.model_family = family;
|
||||
self.config.model = model;
|
||||
}
|
||||
AppEvent::OpenReasoningPopup { model } => {
|
||||
self.chat_widget.open_reasoning_popup(model);
|
||||
|
||||
@@ -465,10 +465,13 @@ impl ChatWidget {
|
||||
fn on_agent_reasoning_final(&mut self) {
|
||||
// At the end of a reasoning block, record transcript-only content.
|
||||
self.full_reasoning_buffer.push_str(&self.reasoning_buffer);
|
||||
let model_family = self
|
||||
.models_manager
|
||||
.construct_model_family(&self.config.model, &self.config);
|
||||
if !self.full_reasoning_buffer.is_empty() {
|
||||
let cell = history_cell::new_reasoning_summary_block(
|
||||
self.full_reasoning_buffer.clone(),
|
||||
&self.config,
|
||||
&model_family,
|
||||
);
|
||||
self.add_boxed_history(cell);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use codex_common::format_env_display::format_env_display;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::types::McpServerTransportConfig;
|
||||
use codex_core::config::types::ReasoningSummaryFormat;
|
||||
use codex_core::openai_models::model_family::ModelFamily;
|
||||
use codex_core::protocol::FileChange;
|
||||
use codex_core::protocol::McpAuthStatus;
|
||||
use codex_core::protocol::McpInvocation;
|
||||
@@ -1420,9 +1421,9 @@ pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistor
|
||||
|
||||
pub(crate) fn new_reasoning_summary_block(
|
||||
full_reasoning_buffer: String,
|
||||
config: &Config,
|
||||
model_family: &ModelFamily,
|
||||
) -> Box<dyn HistoryCell> {
|
||||
if config.model_family.reasoning_summary_format == ReasoningSummaryFormat::Experimental {
|
||||
if model_family.reasoning_summary_format == ReasoningSummaryFormat::Experimental {
|
||||
// Experimental format is following:
|
||||
// ** header **
|
||||
//
|
||||
@@ -1517,12 +1518,15 @@ mod tests {
|
||||
use codex_core::config::ConfigToml;
|
||||
use codex_core::config::types::McpServerConfig;
|
||||
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;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_core::protocol::ExecCommandSource;
|
||||
use mcp_types::CallToolResult;
|
||||
@@ -2320,12 +2324,12 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn reasoning_summary_block() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
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(),
|
||||
&config,
|
||||
&model_family,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
@@ -2337,24 +2341,47 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell =
|
||||
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &config);
|
||||
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &model_family);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
assert_eq!(rendered, vec!["• Detailed reasoning goes here."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_summary_block_falls_back_when_header_is_missing() {
|
||||
fn reasoning_summary_block_respects_config_overrides() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
assert_eq!(
|
||||
model_family.reasoning_summary_format,
|
||||
ReasoningSummaryFormat::Experimental
|
||||
);
|
||||
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
|
||||
&model_family,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);
|
||||
}
|
||||
|
||||
#[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 model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing".to_string(),
|
||||
&config,
|
||||
&model_family,
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
@@ -2363,12 +2390,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reasoning_summary_block_falls_back_when_summary_is_missing() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**".to_string(),
|
||||
&config,
|
||||
&model_family,
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
@@ -2376,7 +2403,7 @@ mod tests {
|
||||
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**\n\n ".to_string(),
|
||||
&config,
|
||||
&model_family,
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
@@ -2385,12 +2412,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reasoning_summary_block_splits_header_and_summary_when_present() {
|
||||
let mut config = test_config();
|
||||
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
|
||||
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
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(),
|
||||
&config,
|
||||
&model_family,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
|
||||
Reference in New Issue
Block a user