mirror of
https://github.com/openai/codex.git
synced 2026-05-29 15:30:22 +00:00
tui: persist primary settings via app server config RPC
This commit is contained in:
@@ -1182,13 +1182,36 @@ impl App {
|
||||
}
|
||||
AppEvent::PersistModelSelection { model, effort } => {
|
||||
let profile = self.active_profile.as_deref();
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_model(Some(model.as_str()), effort)
|
||||
.apply()
|
||||
.await
|
||||
let scoped_key = |key: &str| {
|
||||
if let Some(profile) = profile {
|
||||
format!("profiles.{profile}.{key}")
|
||||
} else {
|
||||
key.to_string()
|
||||
}
|
||||
};
|
||||
let effort_edit = effort.map_or_else(
|
||||
|| crate::config_rpc::clear_config_value(scoped_key("model_reasoning_effort")),
|
||||
|effort| {
|
||||
crate::config_rpc::replace_config_value(
|
||||
scoped_key("model_reasoning_effort"),
|
||||
serde_json::json!(effort.to_string()),
|
||||
)
|
||||
},
|
||||
);
|
||||
match crate::config_rpc::write_config_batch(
|
||||
app_server.request_handle(),
|
||||
vec![
|
||||
crate::config_rpc::replace_config_value(
|
||||
scoped_key("model"),
|
||||
serde_json::json!(model.as_str()),
|
||||
),
|
||||
effort_edit,
|
||||
],
|
||||
/*reload_user_config*/ true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
Ok(_) => {
|
||||
let effort_label = effort
|
||||
.map(|selected_effort| selected_effort.to_string())
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
@@ -1260,13 +1283,22 @@ impl App {
|
||||
}
|
||||
AppEvent::PersistPersonalitySelection { personality } => {
|
||||
let profile = self.active_profile.as_deref();
|
||||
match ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_personality(Some(personality))
|
||||
.apply()
|
||||
.await
|
||||
let key_path = if let Some(profile) = profile {
|
||||
format!("profiles.{profile}.personality")
|
||||
} else {
|
||||
"personality".to_string()
|
||||
};
|
||||
match crate::config_rpc::write_config_batch(
|
||||
app_server.request_handle(),
|
||||
vec![crate::config_rpc::replace_config_value(
|
||||
key_path,
|
||||
serde_json::json!(personality.to_string()),
|
||||
)],
|
||||
/*reload_user_config*/ true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
Ok(_) => {
|
||||
let label = Self::personality_label(personality);
|
||||
let mut message = format!("Personality set to {label}");
|
||||
if let Some(profile) = profile {
|
||||
@@ -1297,15 +1329,45 @@ impl App {
|
||||
self.refresh_status_line();
|
||||
let profile = self.active_profile.as_deref();
|
||||
self.config.service_tier = service_tier.clone();
|
||||
let mut edits = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.set_service_tier(service_tier.clone());
|
||||
let scoped_key = |key: &str| {
|
||||
if let Some(profile) = profile {
|
||||
format!("profiles.{profile}.{key}")
|
||||
} else {
|
||||
key.to_string()
|
||||
}
|
||||
};
|
||||
let mut edits = vec![service_tier.as_ref().map_or_else(
|
||||
|| crate::config_rpc::clear_config_value(scoped_key("service_tier")),
|
||||
|service_tier| {
|
||||
let config_value =
|
||||
match codex_protocol::config_types::ServiceTier::from_request_value(
|
||||
service_tier,
|
||||
) {
|
||||
Some(codex_protocol::config_types::ServiceTier::Fast) => "fast",
|
||||
Some(codex_protocol::config_types::ServiceTier::Flex) => "flex",
|
||||
None => service_tier.as_str(),
|
||||
};
|
||||
crate::config_rpc::replace_config_value(
|
||||
scoped_key("service_tier"),
|
||||
serde_json::json!(config_value),
|
||||
)
|
||||
},
|
||||
)];
|
||||
if service_tier.is_none() {
|
||||
self.config.notices.fast_default_opt_out = Some(true);
|
||||
edits = edits.set_fast_default_opt_out(/*opted_out*/ true);
|
||||
edits.push(crate::config_rpc::replace_config_value(
|
||||
"notice.fast_default_opt_out",
|
||||
serde_json::json!(true),
|
||||
));
|
||||
}
|
||||
match edits.apply().await {
|
||||
Ok(()) => {
|
||||
match crate::config_rpc::write_config_batch(
|
||||
app_server.request_handle(),
|
||||
edits,
|
||||
/*reload_user_config*/ true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let mut message = if let Some(service_tier) = service_tier {
|
||||
format!("Service tier set to {service_tier}")
|
||||
} else {
|
||||
@@ -1468,23 +1530,20 @@ impl App {
|
||||
self.sync_active_thread_permission_settings_to_cached_session()
|
||||
.await;
|
||||
let profile = self.active_profile.as_deref();
|
||||
let segments = if let Some(profile) = profile {
|
||||
vec![
|
||||
"profiles".to_string(),
|
||||
profile.to_string(),
|
||||
"approvals_reviewer".to_string(),
|
||||
]
|
||||
let key_path = if let Some(profile) = profile {
|
||||
format!("profiles.{profile}.approvals_reviewer")
|
||||
} else {
|
||||
vec!["approvals_reviewer".to_string()]
|
||||
"approvals_reviewer".to_string()
|
||||
};
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_profile(profile)
|
||||
.with_edits([ConfigEdit::SetPath {
|
||||
segments,
|
||||
value: policy.to_string().into(),
|
||||
}])
|
||||
.apply()
|
||||
.await
|
||||
if let Err(err) = crate::config_rpc::write_config_batch(
|
||||
app_server.request_handle(),
|
||||
vec![crate::config_rpc::replace_config_value(
|
||||
key_path,
|
||||
serde_json::json!(policy.to_string()),
|
||||
)],
|
||||
/*reload_user_config*/ true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
error = %err,
|
||||
@@ -1575,27 +1634,25 @@ impl App {
|
||||
}
|
||||
AppEvent::PersistPlanModeReasoningEffort(effort) => {
|
||||
let profile = self.active_profile.as_deref();
|
||||
let segments = if let Some(profile) = profile {
|
||||
vec![
|
||||
"profiles".to_string(),
|
||||
profile.to_string(),
|
||||
"plan_mode_reasoning_effort".to_string(),
|
||||
]
|
||||
let key_path = if let Some(profile) = profile {
|
||||
format!("profiles.{profile}.plan_mode_reasoning_effort")
|
||||
} else {
|
||||
vec!["plan_mode_reasoning_effort".to_string()]
|
||||
"plan_mode_reasoning_effort".to_string()
|
||||
};
|
||||
let edit = if let Some(effort) = effort {
|
||||
ConfigEdit::SetPath {
|
||||
segments,
|
||||
value: effort.to_string().into(),
|
||||
}
|
||||
crate::config_rpc::replace_config_value(
|
||||
key_path,
|
||||
serde_json::json!(effort.to_string()),
|
||||
)
|
||||
} else {
|
||||
ConfigEdit::ClearPath { segments }
|
||||
crate::config_rpc::clear_config_value(key_path)
|
||||
};
|
||||
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
|
||||
.with_edits([edit])
|
||||
.apply()
|
||||
.await
|
||||
if let Err(err) = crate::config_rpc::write_config_batch(
|
||||
app_server.request_handle(),
|
||||
vec![edit],
|
||||
/*reload_user_config*/ true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
error = %err,
|
||||
|
||||
79
codex-rs/tui/src/config_rpc.rs
Normal file
79
codex-rs/tui/src/config_rpc.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
//! App-server-backed config persistence helpers for the TUI.
|
||||
//!
|
||||
//! This module centralizes the small typed RPC wrappers the TUI uses when a
|
||||
//! config mutation must be owned by the app server rather than written to the
|
||||
//! local `config.toml` directly.
|
||||
|
||||
use codex_app_server_client::AppServerRequestHandle;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ConfigBatchWriteParams;
|
||||
use codex_app_server_protocol::ConfigEdit;
|
||||
use codex_app_server_protocol::ConfigWriteResponse;
|
||||
use codex_app_server_protocol::MergeStrategy;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::SkillsConfigWriteParams;
|
||||
use codex_app_server_protocol::SkillsConfigWriteResponse;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::WrapErr;
|
||||
use serde_json::Value as JsonValue;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) fn replace_config_value(key_path: impl Into<String>, value: JsonValue) -> ConfigEdit {
|
||||
ConfigEdit {
|
||||
key_path: key_path.into(),
|
||||
value,
|
||||
merge_strategy: MergeStrategy::Replace,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn upsert_config_value(key_path: impl Into<String>, value: JsonValue) -> ConfigEdit {
|
||||
ConfigEdit {
|
||||
key_path: key_path.into(),
|
||||
value,
|
||||
merge_strategy: MergeStrategy::Upsert,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear_config_value(key_path: impl Into<String>) -> ConfigEdit {
|
||||
replace_config_value(key_path, JsonValue::Null)
|
||||
}
|
||||
|
||||
pub(crate) async fn write_config_batch(
|
||||
request_handle: AppServerRequestHandle,
|
||||
edits: Vec<ConfigEdit>,
|
||||
reload_user_config: bool,
|
||||
) -> Result<ConfigWriteResponse> {
|
||||
let request_id = RequestId::String(format!("tui-config-write-{}", Uuid::new_v4()));
|
||||
request_handle
|
||||
.request_typed(ClientRequest::ConfigBatchWrite {
|
||||
request_id,
|
||||
params: ConfigBatchWriteParams {
|
||||
edits,
|
||||
file_path: None,
|
||||
expected_version: None,
|
||||
reload_user_config,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.wrap_err("config/batchWrite failed in TUI")
|
||||
}
|
||||
|
||||
pub(crate) async fn write_skill_enabled(
|
||||
request_handle: AppServerRequestHandle,
|
||||
path: AbsolutePathBuf,
|
||||
enabled: bool,
|
||||
) -> Result<SkillsConfigWriteResponse> {
|
||||
let request_id = RequestId::String(format!("tui-skill-config-write-{}", Uuid::new_v4()));
|
||||
request_handle
|
||||
.request_typed(ClientRequest::SkillsConfigWrite {
|
||||
request_id,
|
||||
params: SkillsConfigWriteParams {
|
||||
path: Some(path),
|
||||
name: None,
|
||||
enabled,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.wrap_err("skills/config/write failed in TUI")
|
||||
}
|
||||
@@ -112,6 +112,7 @@ mod clipboard_copy;
|
||||
mod clipboard_paste;
|
||||
mod collaboration_modes;
|
||||
mod color;
|
||||
mod config_rpc;
|
||||
pub(crate) mod custom_terminal;
|
||||
mod pets;
|
||||
pub use custom_terminal::Terminal;
|
||||
|
||||
Reference in New Issue
Block a user