Wire with_remote_overrides to construct model families (#7621)
- This PR wires `with_remote_overrides` and make the `construct_model_families` an async function - Moves getting model family a level above to keep the function `sync` - Updates the tests to local, offline, and `sync` helper for model families
This commit is contained in:
parent
5f80ad6da8
commit
d08efb1743
16 changed files with 147 additions and 108 deletions
|
|
@ -90,6 +90,7 @@ wildmatch = { workspace = true }
|
|||
|
||||
[features]
|
||||
deterministic_process_ids = []
|
||||
test-support = []
|
||||
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use crate::compact_remote::run_inline_remote_auto_compact_task;
|
|||
use crate::exec_policy::load_exec_policy_for_features;
|
||||
use crate::features::Feature;
|
||||
use crate::features::Features;
|
||||
use crate::openai_models::model_family::ModelFamily;
|
||||
use crate::openai_models::models_manager::ModelsManager;
|
||||
use crate::parse_command::parse_command;
|
||||
use crate::parse_turn_item;
|
||||
|
|
@ -398,35 +399,39 @@ pub(crate) struct SessionSettingsUpdate {
|
|||
}
|
||||
|
||||
impl Session {
|
||||
fn make_turn_context(
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
otel_event_manager: &OtelEventManager,
|
||||
provider: ModelProviderInfo,
|
||||
session_configuration: &SessionConfiguration,
|
||||
conversation_id: ConversationId,
|
||||
sub_id: String,
|
||||
) -> TurnContext {
|
||||
fn build_per_turn_config(session_configuration: &SessionConfiguration) -> Config {
|
||||
let config = session_configuration.original_config_do_not_use.clone();
|
||||
let features = &config.features;
|
||||
let mut per_turn_config = (*config).clone();
|
||||
per_turn_config.model = session_configuration.model.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);
|
||||
per_turn_config.features = config.features.clone();
|
||||
per_turn_config
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn make_turn_context(
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
otel_event_manager: &OtelEventManager,
|
||||
provider: ModelProviderInfo,
|
||||
session_configuration: &SessionConfiguration,
|
||||
mut per_turn_config: Config,
|
||||
model_family: ModelFamily,
|
||||
conversation_id: ConversationId,
|
||||
sub_id: String,
|
||||
) -> TurnContext {
|
||||
if let Some(model_info) = get_model_info(&model_family) {
|
||||
per_turn_config.model_context_window = Some(model_info.context_window);
|
||||
}
|
||||
|
||||
let otel_event_manager = otel_event_manager.clone().with_model(
|
||||
session_configuration.model.as_str(),
|
||||
session_configuration.model.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
);
|
||||
|
||||
let per_turn_config = Arc::new(per_turn_config);
|
||||
let client = ModelClient::new(
|
||||
Arc::new(per_turn_config.clone()),
|
||||
per_turn_config.clone(),
|
||||
auth_manager,
|
||||
model_family.clone(),
|
||||
otel_event_manager,
|
||||
|
|
@ -439,7 +444,7 @@ impl Session {
|
|||
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_family: &model_family,
|
||||
features,
|
||||
features: &per_turn_config.features,
|
||||
});
|
||||
|
||||
TurnContext {
|
||||
|
|
@ -452,14 +457,14 @@ impl Session {
|
|||
user_instructions: session_configuration.user_instructions.clone(),
|
||||
approval_policy: session_configuration.approval_policy,
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
shell_environment_policy: config.shell_environment_policy.clone(),
|
||||
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
final_output_json_schema: None,
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
codex_linux_sandbox_exe: per_turn_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,
|
||||
per_turn_config.as_ref(),
|
||||
model_family.truncation_policy,
|
||||
),
|
||||
}
|
||||
|
|
@ -545,7 +550,9 @@ impl Session {
|
|||
});
|
||||
}
|
||||
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let model_family = models_manager
|
||||
.construct_model_family(&config.model, &config)
|
||||
.await;
|
||||
// todo(aibrahim): why are we passing model here while it can change?
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
|
|
@ -768,12 +775,19 @@ impl Session {
|
|||
session_configuration
|
||||
};
|
||||
|
||||
let per_turn_config = Self::build_per_turn_config(&session_configuration);
|
||||
let model_family = self
|
||||
.services
|
||||
.models_manager
|
||||
.construct_model_family(&per_turn_config.model, &per_turn_config)
|
||||
.await;
|
||||
let mut turn_context: TurnContext = Self::make_turn_context(
|
||||
Some(Arc::clone(&self.services.auth_manager)),
|
||||
Arc::clone(&self.services.models_manager),
|
||||
&self.services.otel_event_manager,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
per_turn_config,
|
||||
model_family,
|
||||
self.conversation_id,
|
||||
sub_id,
|
||||
);
|
||||
|
|
@ -1907,7 +1921,8 @@ async fn spawn_review_thread(
|
|||
let review_model_family = sess
|
||||
.services
|
||||
.models_manager
|
||||
.construct_model_family(&model, &config);
|
||||
.construct_model_family(&model, &config)
|
||||
.await;
|
||||
// For reviews, disable web_search and view_image regardless of global settings.
|
||||
let mut review_features = sess.features.clone();
|
||||
review_features
|
||||
|
|
@ -2812,15 +2827,12 @@ mod tests {
|
|||
fn otel_event_manager(
|
||||
conversation_id: ConversationId,
|
||||
config: &Config,
|
||||
models_manager: &ModelsManager,
|
||||
model_family: &ModelFamily,
|
||||
) -> OtelEventManager {
|
||||
OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
models_manager
|
||||
.construct_model_family(&config.model, config)
|
||||
.slug
|
||||
.as_str(),
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
|
|
@ -2843,9 +2855,6 @@ mod tests {
|
|||
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);
|
||||
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
model: config.model.clone(),
|
||||
|
|
@ -2862,6 +2871,11 @@ mod tests {
|
|||
exec_policy: Arc::new(RwLock::new(ExecPolicy::empty())),
|
||||
session_source: SessionSource::Exec,
|
||||
};
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let model_family =
|
||||
ModelsManager::construct_model_family_offline(&per_turn_config.model, &per_turn_config);
|
||||
let otel_event_manager =
|
||||
otel_event_manager(conversation_id, config.as_ref(), &model_family);
|
||||
|
||||
let state = SessionState::new(session_configuration.clone());
|
||||
|
||||
|
|
@ -2875,16 +2889,17 @@ mod tests {
|
|||
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
|
||||
auth_manager: auth_manager.clone(),
|
||||
otel_event_manager: otel_event_manager.clone(),
|
||||
models_manager: models_manager.clone(),
|
||||
models_manager,
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
};
|
||||
|
||||
let turn_context = Session::make_turn_context(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
models_manager,
|
||||
&otel_event_manager,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
per_turn_config,
|
||||
model_family,
|
||||
conversation_id,
|
||||
"turn_id".to_string(),
|
||||
);
|
||||
|
|
@ -2922,9 +2937,6 @@ mod tests {
|
|||
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);
|
||||
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
model: config.model.clone(),
|
||||
|
|
@ -2941,6 +2953,11 @@ mod tests {
|
|||
exec_policy: Arc::new(RwLock::new(ExecPolicy::empty())),
|
||||
session_source: SessionSource::Exec,
|
||||
};
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let model_family =
|
||||
ModelsManager::construct_model_family_offline(&per_turn_config.model, &per_turn_config);
|
||||
let otel_event_manager =
|
||||
otel_event_manager(conversation_id, config.as_ref(), &model_family);
|
||||
|
||||
let state = SessionState::new(session_configuration.clone());
|
||||
|
||||
|
|
@ -2954,16 +2971,17 @@ mod tests {
|
|||
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
|
||||
auth_manager: Arc::clone(&auth_manager),
|
||||
otel_event_manager: otel_event_manager.clone(),
|
||||
models_manager: models_manager.clone(),
|
||||
models_manager,
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
};
|
||||
|
||||
let turn_context = Arc::new(Session::make_turn_context(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
models_manager,
|
||||
&otel_event_manager,
|
||||
session_configuration.provider.clone(),
|
||||
&session_configuration,
|
||||
per_turn_config,
|
||||
model_family,
|
||||
conversation_id,
|
||||
"turn_id".to_string(),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -61,7 +61,14 @@ impl ModelsManager {
|
|||
Ok(models)
|
||||
}
|
||||
|
||||
pub fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily {
|
||||
pub async fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily {
|
||||
find_family_for_model(model)
|
||||
.with_config_overrides(config)
|
||||
.with_remote_overrides(self.remote_models.read().await.clone())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn construct_model_family_offline(model: &str, config: &Config) -> ModelFamily {
|
||||
find_family_for_model(model).with_config_overrides(config)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,9 @@ pub(crate) async fn assess_command(
|
|||
output_schema: Some(sandbox_assessment_schema()),
|
||||
};
|
||||
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let model_family = models_manager
|
||||
.construct_model_family(&config.model, &config)
|
||||
.await;
|
||||
|
||||
let child_otel = parent_otel.with_model(config.model.as_str(), model_family.slug.as_str());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
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;
|
||||
|
|
@ -73,9 +71,7 @@ async fn run_request(input: Vec<ResponseItem>) -> Value {
|
|||
let config = Arc::new(config);
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
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 model_family = ModelsManager::construct_model_family_offline(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
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;
|
||||
|
||||
|
|
@ -12,6 +11,7 @@ use codex_core::Prompt;
|
|||
use codex_core::ResponseEvent;
|
||||
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;
|
||||
|
|
@ -74,8 +74,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
|
|||
let conversation_id = ConversationId::new();
|
||||
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 model_family = ModelsManager::construct_model_family_offline(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ path = "lib.rs"
|
|||
anyhow = { workspace = true }
|
||||
assert_cmd = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-core = { workspace = true, features = ["test-support"] }
|
||||
codex-protocol = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
regex-lite = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -65,9 +65,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
|||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ChatGPT;
|
||||
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 model_family = ModelsManager::construct_model_family_offline(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
config.model.as_str(),
|
||||
|
|
@ -157,9 +155,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
|||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ChatGPT;
|
||||
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 model_family = ModelsManager::construct_model_family_offline(&config.model, &config);
|
||||
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
|
|
@ -250,16 +246,16 @@ async fn responses_respects_model_family_overrides_from_config() {
|
|||
let config = Arc::new(config);
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
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 auth_mode =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")).get_auth_mode();
|
||||
let model_family = ModelsManager::construct_model_family_offline(&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()),
|
||||
auth_manager.get_auth_mode(),
|
||||
auth_mode,
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1015,11 +1015,9 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
|||
let effort = config.model_reasoning_effort;
|
||||
let summary = config.model_reasoning_summary;
|
||||
let config = Arc::new(config);
|
||||
|
||||
let model_family = ModelsManager::construct_model_family_offline(&config.model, &config);
|
||||
let conversation_id = ConversationId::new();
|
||||
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,
|
||||
config.model.as_str(),
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
|||
let base_instructions = conversation_manager
|
||||
.get_models_manager()
|
||||
.construct_model_family(&config.model, &config)
|
||||
.await
|
||||
.base_instructions
|
||||
.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ arboard = { workspace = true }
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
codex-core = { workspace = true, features = ["test-support"] }
|
||||
assert_matches = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
insta = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -302,7 +302,10 @@ impl App {
|
|||
};
|
||||
|
||||
let enhanced_keys_supported = tui.enhanced_keys_supported();
|
||||
|
||||
let model_family = conversation_manager
|
||||
.get_models_manager()
|
||||
.construct_model_family(&config.model, &config)
|
||||
.await;
|
||||
let mut chat_widget = match resume_selection {
|
||||
ResumeSelection::StartFresh | ResumeSelection::Exit => {
|
||||
let init = crate::chatwidget::ChatWidgetInit {
|
||||
|
|
@ -317,6 +320,7 @@ impl App {
|
|||
feedback: feedback.clone(),
|
||||
skills: skills.clone(),
|
||||
is_first_run,
|
||||
model_family,
|
||||
};
|
||||
ChatWidget::new(init, conversation_manager.clone())
|
||||
}
|
||||
|
|
@ -343,6 +347,7 @@ impl App {
|
|||
feedback: feedback.clone(),
|
||||
skills: skills.clone(),
|
||||
is_first_run,
|
||||
model_family,
|
||||
};
|
||||
ChatWidget::new_from_existing(
|
||||
init,
|
||||
|
|
@ -481,6 +486,11 @@ impl App {
|
|||
}
|
||||
|
||||
async fn handle_event(&mut self, tui: &mut tui::Tui, event: AppEvent) -> Result<bool> {
|
||||
let model_family = self
|
||||
.server
|
||||
.get_models_manager()
|
||||
.construct_model_family(&self.config.model, &self.config)
|
||||
.await;
|
||||
match event {
|
||||
AppEvent::NewSession => {
|
||||
let summary = session_summary(
|
||||
|
|
@ -500,6 +510,7 @@ impl App {
|
|||
feedback: self.feedback.clone(),
|
||||
skills: self.skills.clone(),
|
||||
is_first_run: false,
|
||||
model_family,
|
||||
};
|
||||
self.chat_widget = ChatWidget::new(init, self.server.clone());
|
||||
if let Some(summary) = summary {
|
||||
|
|
@ -549,6 +560,7 @@ impl App {
|
|||
feedback: self.feedback.clone(),
|
||||
skills: self.skills.clone(),
|
||||
is_first_run: false,
|
||||
model_family: model_family.clone(),
|
||||
};
|
||||
self.chat_widget = ChatWidget::new_from_existing(
|
||||
init,
|
||||
|
|
@ -677,7 +689,12 @@ impl App {
|
|||
self.on_update_reasoning_effort(effort);
|
||||
}
|
||||
AppEvent::UpdateModel(model) => {
|
||||
self.chat_widget.set_model(&model);
|
||||
let model_family = self
|
||||
.server
|
||||
.get_models_manager()
|
||||
.construct_model_family(&model, &self.config)
|
||||
.await;
|
||||
self.chat_widget.set_model(&model, model_family);
|
||||
self.config.model = model;
|
||||
}
|
||||
AppEvent::OpenReasoningPopup { model } => {
|
||||
|
|
|
|||
|
|
@ -340,6 +340,7 @@ impl App {
|
|||
let session_configured = new_conv.session_configured;
|
||||
let init = crate::chatwidget::ChatWidgetInit {
|
||||
config: cfg,
|
||||
model_family: self.chat_widget.get_model_family(),
|
||||
frame_requester: tui.frame_requester(),
|
||||
app_event_tx: self.app_event_tx.clone(),
|
||||
initial_prompt: None,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use codex_core::config::Config;
|
|||
use codex_core::config::types::Notifications;
|
||||
use codex_core::git_info::current_branch_name;
|
||||
use codex_core::git_info::local_git_branches;
|
||||
use codex_core::openai_models::model_family::ModelFamily;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
||||
use codex_core::protocol::AgentMessageDeltaEvent;
|
||||
|
|
@ -261,6 +262,7 @@ pub(crate) struct ChatWidgetInit {
|
|||
pub(crate) feedback: codex_feedback::CodexFeedback,
|
||||
pub(crate) skills: Option<Vec<SkillMetadata>>,
|
||||
pub(crate) is_first_run: bool,
|
||||
pub(crate) model_family: ModelFamily,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -277,6 +279,7 @@ pub(crate) struct ChatWidget {
|
|||
bottom_pane: BottomPane,
|
||||
active_cell: Option<Box<dyn HistoryCell>>,
|
||||
config: Config,
|
||||
model_family: ModelFamily,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
models_manager: Arc<ModelsManager>,
|
||||
session_header: SessionHeader,
|
||||
|
|
@ -465,15 +468,13 @@ impl ChatWidget {
|
|||
}
|
||||
|
||||
fn on_agent_reasoning_final(&mut self) {
|
||||
let reasoning_summary_format = self.get_model_family().reasoning_summary_format;
|
||||
// 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(),
|
||||
&model_family,
|
||||
reasoning_summary_format,
|
||||
);
|
||||
self.add_boxed_history(cell);
|
||||
}
|
||||
|
|
@ -647,6 +648,9 @@ impl ChatWidget {
|
|||
self.stream_controller = None;
|
||||
self.maybe_show_pending_rate_limit_prompt();
|
||||
}
|
||||
pub(crate) fn get_model_family(&self) -> ModelFamily {
|
||||
self.model_family.clone()
|
||||
}
|
||||
|
||||
fn on_error(&mut self, message: String) {
|
||||
self.finalize_turn();
|
||||
|
|
@ -1249,6 +1253,7 @@ impl ChatWidget {
|
|||
feedback,
|
||||
skills,
|
||||
is_first_run,
|
||||
model_family,
|
||||
} = common;
|
||||
let mut rng = rand::rng();
|
||||
let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string();
|
||||
|
|
@ -1270,6 +1275,7 @@ impl ChatWidget {
|
|||
}),
|
||||
active_cell: None,
|
||||
config: config.clone(),
|
||||
model_family,
|
||||
auth_manager,
|
||||
models_manager,
|
||||
session_header: SessionHeader::new(config.model),
|
||||
|
|
@ -1329,6 +1335,7 @@ impl ChatWidget {
|
|||
models_manager,
|
||||
feedback,
|
||||
skills,
|
||||
model_family,
|
||||
..
|
||||
} = common;
|
||||
let mut rng = rand::rng();
|
||||
|
|
@ -1353,6 +1360,7 @@ impl ChatWidget {
|
|||
}),
|
||||
active_cell: None,
|
||||
config: config.clone(),
|
||||
model_family,
|
||||
auth_manager,
|
||||
models_manager,
|
||||
session_header: SessionHeader::new(config.model),
|
||||
|
|
@ -1785,7 +1793,7 @@ impl ChatWidget {
|
|||
EventMsg::AgentReasoning(AgentReasoningEvent { .. }) => self.on_agent_reasoning_final(),
|
||||
EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent { text }) => {
|
||||
self.on_agent_reasoning_delta(text);
|
||||
self.on_agent_reasoning_final()
|
||||
self.on_agent_reasoning_final();
|
||||
}
|
||||
EventMsg::AgentReasoningSectionBreak(_) => self.on_reasoning_section_break(),
|
||||
EventMsg::TaskStarted(_) => self.on_task_started(),
|
||||
|
|
@ -2843,9 +2851,10 @@ impl ChatWidget {
|
|||
}
|
||||
|
||||
/// Set the model in the widget's config copy.
|
||||
pub(crate) fn set_model(&mut self, model: &str) {
|
||||
pub(crate) fn set_model(&mut self, model: &str, model_family: ModelFamily) {
|
||||
self.session_header.set_model(model);
|
||||
self.config.model = model.to_string();
|
||||
self.model_family = model_family;
|
||||
}
|
||||
|
||||
pub(crate) fn add_info_message(&mut self, message: String, hint: Option<String>) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use codex_core::CodexAuth;
|
|||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::ConfigToml;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use codex_core::protocol::AgentMessageDeltaEvent;
|
||||
use codex_core::protocol::AgentMessageEvent;
|
||||
use codex_core::protocol::AgentReasoningDeltaEvent;
|
||||
|
|
@ -345,6 +346,7 @@ async fn helpers_are_available_and_do_not_panic() {
|
|||
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
||||
let tx = AppEventSender::new(tx_raw);
|
||||
let cfg = test_config();
|
||||
let model_family = ModelsManager::construct_model_family_offline(&cfg.model, &cfg);
|
||||
let conversation_manager = Arc::new(ConversationManager::with_auth(CodexAuth::from_api_key(
|
||||
"test",
|
||||
)));
|
||||
|
|
@ -361,6 +363,7 @@ async fn helpers_are_available_and_do_not_panic() {
|
|||
feedback: codex_feedback::CodexFeedback::new(),
|
||||
skills: None,
|
||||
is_first_run: true,
|
||||
model_family,
|
||||
};
|
||||
let mut w = ChatWidget::new(init, conversation_manager);
|
||||
// Basic construction sanity.
|
||||
|
|
@ -394,6 +397,7 @@ fn make_chatwidget_manual() -> (
|
|||
bottom_pane: bottom,
|
||||
active_cell: None,
|
||||
config: cfg.clone(),
|
||||
model_family: ModelsManager::construct_model_family_offline(&cfg.model, &cfg),
|
||||
auth_manager: auth_manager.clone(),
|
||||
models_manager: Arc::new(ModelsManager::new(auth_manager)),
|
||||
session_header: SessionHeader::new(cfg.model),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ 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;
|
||||
|
|
@ -1421,9 +1420,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,
|
||||
model_family: &ModelFamily,
|
||||
reasoning_summary_format: ReasoningSummaryFormat,
|
||||
) -> Box<dyn HistoryCell> {
|
||||
if model_family.reasoning_summary_format == ReasoningSummaryFormat::Experimental {
|
||||
if reasoning_summary_format == ReasoningSummaryFormat::Experimental {
|
||||
// Experimental format is following:
|
||||
// ** header **
|
||||
//
|
||||
|
|
@ -1513,8 +1512,6 @@ 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;
|
||||
|
|
@ -1527,7 +1524,6 @@ mod tests {
|
|||
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;
|
||||
|
|
@ -2326,13 +2322,12 @@ mod tests {
|
|||
#[test]
|
||||
fn reasoning_summary_block() {
|
||||
let config = test_config();
|
||||
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 reasoning_format =
|
||||
ModelsManager::construct_model_family_offline(&config.model, &config)
|
||||
.reasoning_summary_format;
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
|
||||
&model_family,
|
||||
reasoning_format,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
|
|
@ -2345,12 +2340,13 @@ mod tests {
|
|||
#[test]
|
||||
fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() {
|
||||
let config = test_config();
|
||||
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);
|
||||
let reasoning_format =
|
||||
ModelsManager::construct_model_family_offline(&config.model, &config)
|
||||
.reasoning_summary_format;
|
||||
let cell = new_reasoning_summary_block(
|
||||
"Detailed reasoning goes here.".to_string(),
|
||||
reasoning_format,
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
assert_eq!(rendered, vec!["• Detailed reasoning goes here."]);
|
||||
|
|
@ -2362,11 +2358,7 @@ 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 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 model_family = ModelsManager::construct_model_family_offline(&config.model, &config);
|
||||
assert_eq!(
|
||||
model_family.reasoning_summary_format,
|
||||
ReasoningSummaryFormat::Experimental
|
||||
|
|
@ -2374,7 +2366,7 @@ mod tests {
|
|||
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
|
||||
&model_family,
|
||||
model_family.reasoning_summary_format,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
|
|
@ -2384,13 +2376,12 @@ mod tests {
|
|||
#[test]
|
||||
fn reasoning_summary_block_falls_back_when_header_is_missing() {
|
||||
let config = test_config();
|
||||
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 reasoning_format =
|
||||
ModelsManager::construct_model_family_offline(&config.model, &config)
|
||||
.reasoning_summary_format;
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing".to_string(),
|
||||
&model_family,
|
||||
reasoning_format,
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
|
|
@ -2400,13 +2391,12 @@ mod tests {
|
|||
#[test]
|
||||
fn reasoning_summary_block_falls_back_when_summary_is_missing() {
|
||||
let config = test_config();
|
||||
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 reasoning_format =
|
||||
ModelsManager::construct_model_family_offline(&config.model, &config)
|
||||
.reasoning_summary_format;
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**".to_string(),
|
||||
&model_family,
|
||||
reasoning_format.clone(),
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
|
|
@ -2414,7 +2404,7 @@ mod tests {
|
|||
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**\n\n ".to_string(),
|
||||
&model_family,
|
||||
reasoning_format,
|
||||
);
|
||||
|
||||
let rendered = render_transcript(cell.as_ref());
|
||||
|
|
@ -2424,13 +2414,12 @@ mod tests {
|
|||
#[test]
|
||||
fn reasoning_summary_block_splits_header_and_summary_when_present() {
|
||||
let config = test_config();
|
||||
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 reasoning_format =
|
||||
ModelsManager::construct_model_family_offline(&config.model, &config)
|
||||
.reasoning_summary_format;
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level plan**\n\nWe should fix the bug next.".to_string(),
|
||||
&model_family,
|
||||
reasoning_format,
|
||||
);
|
||||
|
||||
let rendered_display = render_lines(&cell.display_lines(80));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue