chore: config editor (#5878)
The goal is to have a single place where we actually write files In a follow-up PR, will move everything config related in a dedicated module and move the helpers in a dedicated file
This commit is contained in:
@@ -74,9 +74,7 @@ use codex_core::config::Config;
|
|||||||
use codex_core::config::ConfigOverrides;
|
use codex_core::config::ConfigOverrides;
|
||||||
use codex_core::config::ConfigToml;
|
use codex_core::config::ConfigToml;
|
||||||
use codex_core::config::load_config_as_toml;
|
use codex_core::config::load_config_as_toml;
|
||||||
use codex_core::config_edit::CONFIG_KEY_EFFORT;
|
use codex_core::config_edit::ConfigEditsBuilder;
|
||||||
use codex_core::config_edit::CONFIG_KEY_MODEL;
|
|
||||||
use codex_core::config_edit::persist_overrides_and_clear_if_none;
|
|
||||||
use codex_core::default_client::get_codex_user_agent;
|
use codex_core::default_client::get_codex_user_agent;
|
||||||
use codex_core::exec::ExecParams;
|
use codex_core::exec::ExecParams;
|
||||||
use codex_core::exec_env::create_env;
|
use codex_core::exec_env::create_env;
|
||||||
@@ -689,19 +687,12 @@ impl CodexMessageProcessor {
|
|||||||
model,
|
model,
|
||||||
reasoning_effort,
|
reasoning_effort,
|
||||||
} = params;
|
} = params;
|
||||||
let effort_str = reasoning_effort.map(|effort| effort.to_string());
|
|
||||||
|
|
||||||
let overrides: [(&[&str], Option<&str>); 2] = [
|
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||||
(&[CONFIG_KEY_MODEL], model.as_deref()),
|
.with_profile(self.config.active_profile.as_deref())
|
||||||
(&[CONFIG_KEY_EFFORT], effort_str.as_deref()),
|
.set_model(model.as_deref(), reasoning_effort)
|
||||||
];
|
.apply()
|
||||||
|
.await
|
||||||
match persist_overrides_and_clear_if_none(
|
|
||||||
&self.config.codex_home,
|
|
||||||
self.config.active_profile.as_deref(),
|
|
||||||
&overrides,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let response = SetDefaultModelResponse {};
|
let response = SetDefaultModelResponse {};
|
||||||
@@ -710,7 +701,7 @@ impl CodexMessageProcessor {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error = JSONRPCErrorError {
|
let error = JSONRPCErrorError {
|
||||||
code: INTERNAL_ERROR_CODE,
|
code: INTERNAL_ERROR_CODE,
|
||||||
message: format!("failed to persist overrides: {err}"),
|
message: format!("failed to persist model selection: {err}"),
|
||||||
data: None,
|
data: None,
|
||||||
};
|
};
|
||||||
self.outgoing.send_error(request_id, error).await;
|
self.outgoing.send_error(request_id, error).await;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use codex_core::config::Config;
|
|||||||
use codex_core::config::ConfigOverrides;
|
use codex_core::config::ConfigOverrides;
|
||||||
use codex_core::config::find_codex_home;
|
use codex_core::config::find_codex_home;
|
||||||
use codex_core::config::load_global_mcp_servers;
|
use codex_core::config::load_global_mcp_servers;
|
||||||
use codex_core::config::write_global_mcp_servers;
|
use codex_core::config_edit::ConfigEditsBuilder;
|
||||||
use codex_core::config_types::McpServerConfig;
|
use codex_core::config_types::McpServerConfig;
|
||||||
use codex_core::config_types::McpServerTransportConfig;
|
use codex_core::config_types::McpServerTransportConfig;
|
||||||
use codex_core::features::Feature;
|
use codex_core::features::Feature;
|
||||||
@@ -263,7 +263,10 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
|
|||||||
|
|
||||||
servers.insert(name.clone(), new_entry);
|
servers.insert(name.clone(), new_entry);
|
||||||
|
|
||||||
write_global_mcp_servers(&codex_home, &servers)
|
ConfigEditsBuilder::new(&codex_home)
|
||||||
|
.replace_mcp_servers(&servers)
|
||||||
|
.apply()
|
||||||
|
.await
|
||||||
.with_context(|| format!("failed to write MCP servers to {}", codex_home.display()))?;
|
.with_context(|| format!("failed to write MCP servers to {}", codex_home.display()))?;
|
||||||
|
|
||||||
println!("Added global MCP server '{name}'.");
|
println!("Added global MCP server '{name}'.");
|
||||||
@@ -321,7 +324,10 @@ async fn run_remove(config_overrides: &CliConfigOverrides, remove_args: RemoveAr
|
|||||||
let removed = servers.remove(&name).is_some();
|
let removed = servers.remove(&name).is_some();
|
||||||
|
|
||||||
if removed {
|
if removed {
|
||||||
write_global_mcp_servers(&codex_home, &servers)
|
ConfigEditsBuilder::new(&codex_home)
|
||||||
|
.replace_mcp_servers(&servers)
|
||||||
|
.apply()
|
||||||
|
.await
|
||||||
.with_context(|| format!("failed to write MCP servers to {}", codex_home.display()))?;
|
.with_context(|| format!("failed to write MCP servers to {}", codex_home.display()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_core::config::load_global_mcp_servers;
|
use codex_core::config::load_global_mcp_servers;
|
||||||
use codex_core::config::write_global_mcp_servers;
|
use codex_core::config_edit::ConfigEditsBuilder;
|
||||||
use codex_core::config_types::McpServerTransportConfig;
|
use codex_core::config_types::McpServerTransportConfig;
|
||||||
use predicates::prelude::PredicateBooleanExt;
|
use predicates::prelude::PredicateBooleanExt;
|
||||||
use predicates::str::contains;
|
use predicates::str::contains;
|
||||||
@@ -59,7 +59,9 @@ async fn list_and_get_render_expected_output() -> Result<()> {
|
|||||||
}
|
}
|
||||||
other => panic!("unexpected transport: {other:?}"),
|
other => panic!("unexpected transport: {other:?}"),
|
||||||
}
|
}
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
ConfigEditsBuilder::new(codex_home.path())
|
||||||
|
.replace_mcp_servers(&servers)
|
||||||
|
.apply_blocking()?;
|
||||||
|
|
||||||
let mut list_cmd = codex_command(codex_home.path())?;
|
let mut list_cmd = codex_command(codex_home.path())?;
|
||||||
let list_output = list_cmd.args(["mcp", "list"]).output()?;
|
let list_output = list_cmd.args(["mcp", "list"]).output()?;
|
||||||
@@ -149,7 +151,9 @@ async fn get_disabled_server_shows_single_line() -> Result<()> {
|
|||||||
.get_mut("docs")
|
.get_mut("docs")
|
||||||
.expect("docs server should exist after add");
|
.expect("docs server should exist after add");
|
||||||
docs.enabled = false;
|
docs.enabled = false;
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
ConfigEditsBuilder::new(codex_home.path())
|
||||||
|
.replace_mcp_servers(&servers)
|
||||||
|
.apply_blocking()?;
|
||||||
|
|
||||||
let mut get_cmd = codex_command(codex_home.path())?;
|
let mut get_cmd = codex_command(codex_home.path())?;
|
||||||
let get_output = get_cmd.args(["mcp", "get", "docs"]).output()?;
|
let get_output = get_cmd.args(["mcp", "get", "docs"]).output()?;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use crate::config_profile::ConfigProfile;
|
|||||||
use crate::config_types::DEFAULT_OTEL_ENVIRONMENT;
|
use crate::config_types::DEFAULT_OTEL_ENVIRONMENT;
|
||||||
use crate::config_types::History;
|
use crate::config_types::History;
|
||||||
use crate::config_types::McpServerConfig;
|
use crate::config_types::McpServerConfig;
|
||||||
use crate::config_types::McpServerTransportConfig;
|
|
||||||
use crate::config_types::Notice;
|
use crate::config_types::Notice;
|
||||||
use crate::config_types::Notifications;
|
use crate::config_types::Notifications;
|
||||||
use crate::config_types::OtelConfig;
|
use crate::config_types::OtelConfig;
|
||||||
@@ -34,7 +33,6 @@ use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
|||||||
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
|
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
|
||||||
use crate::protocol::AskForApproval;
|
use crate::protocol::AskForApproval;
|
||||||
use crate::protocol::SandboxPolicy;
|
use crate::protocol::SandboxPolicy;
|
||||||
use anyhow::Context;
|
|
||||||
use codex_app_server_protocol::Tools;
|
use codex_app_server_protocol::Tools;
|
||||||
use codex_app_server_protocol::UserSavedConfig;
|
use codex_app_server_protocol::UserSavedConfig;
|
||||||
use codex_protocol::config_types::ForcedLoginMethod;
|
use codex_protocol::config_types::ForcedLoginMethod;
|
||||||
@@ -53,12 +51,8 @@ use std::io::ErrorKind;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
use toml::Value as TomlValue;
|
use toml::Value as TomlValue;
|
||||||
use toml_edit::Array as TomlArray;
|
|
||||||
use toml_edit::DocumentMut;
|
use toml_edit::DocumentMut;
|
||||||
use toml_edit::Item as TomlItem;
|
|
||||||
use toml_edit::Table as TomlTable;
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub const OPENAI_DEFAULT_MODEL: &str = "gpt-5";
|
pub const OPENAI_DEFAULT_MODEL: &str = "gpt-5";
|
||||||
@@ -383,141 +377,10 @@ fn ensure_no_inline_bearer_tokens(value: &TomlValue) -> std::io::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_global_mcp_servers(
|
pub(crate) fn set_project_trusted_inner(
|
||||||
codex_home: &Path,
|
doc: &mut DocumentMut,
|
||||||
servers: &BTreeMap<String, McpServerConfig>,
|
project_path: &Path,
|
||||||
) -> std::io::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
|
||||||
let mut doc = match std::fs::read_to_string(&config_path) {
|
|
||||||
Ok(contents) => contents
|
|
||||||
.parse::<DocumentMut>()
|
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?,
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
doc.as_table_mut().remove("mcp_servers");
|
|
||||||
|
|
||||||
if !servers.is_empty() {
|
|
||||||
let mut table = TomlTable::new();
|
|
||||||
table.set_implicit(true);
|
|
||||||
doc["mcp_servers"] = TomlItem::Table(table);
|
|
||||||
|
|
||||||
for (name, config) in servers {
|
|
||||||
let mut entry = TomlTable::new();
|
|
||||||
entry.set_implicit(false);
|
|
||||||
match &config.transport {
|
|
||||||
McpServerTransportConfig::Stdio {
|
|
||||||
command,
|
|
||||||
args,
|
|
||||||
env,
|
|
||||||
env_vars,
|
|
||||||
cwd,
|
|
||||||
} => {
|
|
||||||
entry["command"] = toml_edit::value(command.clone());
|
|
||||||
|
|
||||||
if !args.is_empty() {
|
|
||||||
let mut args_array = TomlArray::new();
|
|
||||||
for arg in args {
|
|
||||||
args_array.push(arg.clone());
|
|
||||||
}
|
|
||||||
entry["args"] = TomlItem::Value(args_array.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(env) = env
|
|
||||||
&& !env.is_empty()
|
|
||||||
{
|
|
||||||
let mut env_table = TomlTable::new();
|
|
||||||
env_table.set_implicit(false);
|
|
||||||
let mut pairs: Vec<_> = env.iter().collect();
|
|
||||||
pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
for (key, value) in pairs {
|
|
||||||
env_table.insert(key, toml_edit::value(value.clone()));
|
|
||||||
}
|
|
||||||
entry["env"] = TomlItem::Table(env_table);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !env_vars.is_empty() {
|
|
||||||
entry["env_vars"] =
|
|
||||||
TomlItem::Value(env_vars.iter().collect::<TomlArray>().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cwd) = cwd {
|
|
||||||
entry["cwd"] = toml_edit::value(cwd.to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
McpServerTransportConfig::StreamableHttp {
|
|
||||||
url,
|
|
||||||
bearer_token_env_var,
|
|
||||||
http_headers,
|
|
||||||
env_http_headers,
|
|
||||||
} => {
|
|
||||||
entry["url"] = toml_edit::value(url.clone());
|
|
||||||
if let Some(env_var) = bearer_token_env_var {
|
|
||||||
entry["bearer_token_env_var"] = toml_edit::value(env_var.clone());
|
|
||||||
}
|
|
||||||
if let Some(headers) = http_headers
|
|
||||||
&& !headers.is_empty()
|
|
||||||
{
|
|
||||||
let mut table = TomlTable::new();
|
|
||||||
table.set_implicit(false);
|
|
||||||
let mut pairs: Vec<_> = headers.iter().collect();
|
|
||||||
pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
for (key, value) in pairs {
|
|
||||||
table.insert(key, toml_edit::value(value.clone()));
|
|
||||||
}
|
|
||||||
entry["http_headers"] = TomlItem::Table(table);
|
|
||||||
}
|
|
||||||
if let Some(headers) = env_http_headers
|
|
||||||
&& !headers.is_empty()
|
|
||||||
{
|
|
||||||
let mut table = TomlTable::new();
|
|
||||||
table.set_implicit(false);
|
|
||||||
let mut pairs: Vec<_> = headers.iter().collect();
|
|
||||||
pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
for (key, value) in pairs {
|
|
||||||
table.insert(key, toml_edit::value(value.clone()));
|
|
||||||
}
|
|
||||||
entry["env_http_headers"] = TomlItem::Table(table);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.enabled {
|
|
||||||
entry["enabled"] = toml_edit::value(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(timeout) = config.startup_timeout_sec {
|
|
||||||
entry["startup_timeout_sec"] = toml_edit::value(timeout.as_secs_f64());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(timeout) = config.tool_timeout_sec {
|
|
||||||
entry["tool_timeout_sec"] = toml_edit::value(timeout.as_secs_f64());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(enabled_tools) = &config.enabled_tools {
|
|
||||||
entry["enabled_tools"] =
|
|
||||||
TomlItem::Value(enabled_tools.iter().collect::<TomlArray>().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(disabled_tools) = &config.disabled_tools {
|
|
||||||
entry["disabled_tools"] =
|
|
||||||
TomlItem::Value(disabled_tools.iter().collect::<TomlArray>().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
doc["mcp_servers"][name.as_str()] = TomlItem::Table(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::create_dir_all(codex_home)?;
|
|
||||||
let tmp_file = NamedTempFile::new_in(codex_home)?;
|
|
||||||
std::fs::write(tmp_file.path(), doc.to_string())?;
|
|
||||||
tmp_file.persist(config_path).map_err(|err| err.error)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_project_trusted_inner(doc: &mut DocumentMut, project_path: &Path) -> anyhow::Result<()> {
|
|
||||||
// Ensure we render a human-friendly structure:
|
// Ensure we render a human-friendly structure:
|
||||||
//
|
//
|
||||||
// [projects]
|
// [projects]
|
||||||
@@ -585,209 +448,11 @@ fn set_project_trusted_inner(doc: &mut DocumentMut, project_path: &Path) -> anyh
|
|||||||
/// Patch `CODEX_HOME/config.toml` project state.
|
/// Patch `CODEX_HOME/config.toml` project state.
|
||||||
/// Use with caution.
|
/// Use with caution.
|
||||||
pub fn set_project_trusted(codex_home: &Path, project_path: &Path) -> anyhow::Result<()> {
|
pub fn set_project_trusted(codex_home: &Path, project_path: &Path) -> anyhow::Result<()> {
|
||||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
use crate::config_edit::ConfigEditsBuilder;
|
||||||
// Parse existing config if present; otherwise start a new document.
|
|
||||||
let mut doc = match std::fs::read_to_string(config_path.clone()) {
|
|
||||||
Ok(s) => s.parse::<DocumentMut>()?,
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
set_project_trusted_inner(&mut doc, project_path)?;
|
ConfigEditsBuilder::new(codex_home)
|
||||||
|
.set_project_trusted(project_path)
|
||||||
// ensure codex_home exists
|
.apply_blocking()
|
||||||
std::fs::create_dir_all(codex_home)?;
|
|
||||||
|
|
||||||
// create a tmp_file
|
|
||||||
let tmp_file = NamedTempFile::new_in(codex_home)?;
|
|
||||||
std::fs::write(tmp_file.path(), doc.to_string())?;
|
|
||||||
|
|
||||||
// atomically move the tmp file into config.toml
|
|
||||||
tmp_file.persist(config_path)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist the acknowledgement flag for the Windows onboarding screen.
|
|
||||||
pub fn set_windows_wsl_setup_acknowledged(
|
|
||||||
codex_home: &Path,
|
|
||||||
acknowledged: bool,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
|
||||||
let mut doc = match std::fs::read_to_string(config_path.clone()) {
|
|
||||||
Ok(s) => s.parse::<DocumentMut>()?,
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
doc["windows_wsl_setup_acknowledged"] = toml_edit::value(acknowledged);
|
|
||||||
|
|
||||||
std::fs::create_dir_all(codex_home)?;
|
|
||||||
|
|
||||||
let tmp_file = NamedTempFile::new_in(codex_home)?;
|
|
||||||
std::fs::write(tmp_file.path(), doc.to_string())?;
|
|
||||||
tmp_file.persist(config_path)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist the acknowledgement flag for the full access warning prompt.
|
|
||||||
pub fn set_hide_full_access_warning(codex_home: &Path, acknowledged: bool) -> anyhow::Result<()> {
|
|
||||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
|
||||||
let mut doc = match std::fs::read_to_string(config_path.clone()) {
|
|
||||||
Ok(s) => s.parse::<DocumentMut>()?,
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let notices_table = load_or_create_top_level_table(&mut doc, Notice::TABLE_KEY)?;
|
|
||||||
|
|
||||||
notices_table["hide_full_access_warning"] = toml_edit::value(acknowledged);
|
|
||||||
|
|
||||||
std::fs::create_dir_all(codex_home)?;
|
|
||||||
let tmp_file = NamedTempFile::new_in(codex_home)?;
|
|
||||||
std::fs::write(tmp_file.path(), doc.to_string())?;
|
|
||||||
tmp_file.persist(config_path)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_or_create_top_level_table<'a>(
|
|
||||||
doc: &'a mut DocumentMut,
|
|
||||||
key: &str,
|
|
||||||
) -> anyhow::Result<&'a mut toml_edit::Table> {
|
|
||||||
let mut created_table = false;
|
|
||||||
|
|
||||||
let root = doc.as_table_mut();
|
|
||||||
let needs_table =
|
|
||||||
!root.contains_key(key) || root.get(key).and_then(|item| item.as_table()).is_none();
|
|
||||||
if needs_table {
|
|
||||||
root.insert(key, toml_edit::table());
|
|
||||||
created_table = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(table) = doc[key].as_table_mut() else {
|
|
||||||
return Err(anyhow::anyhow!(format!(
|
|
||||||
"table [{key}] missing after initialization"
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
|
|
||||||
if created_table {
|
|
||||||
table.set_implicit(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_profile_table<'a>(
|
|
||||||
doc: &'a mut DocumentMut,
|
|
||||||
profile_name: &str,
|
|
||||||
) -> anyhow::Result<&'a mut toml_edit::Table> {
|
|
||||||
let mut created_profiles_table = false;
|
|
||||||
{
|
|
||||||
let root = doc.as_table_mut();
|
|
||||||
let needs_table = !root.contains_key("profiles")
|
|
||||||
|| root
|
|
||||||
.get("profiles")
|
|
||||||
.and_then(|item| item.as_table())
|
|
||||||
.is_none();
|
|
||||||
if needs_table {
|
|
||||||
root.insert("profiles", toml_edit::table());
|
|
||||||
created_profiles_table = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(profiles_table) = doc["profiles"].as_table_mut() else {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"profiles table missing after initialization"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
if created_profiles_table {
|
|
||||||
profiles_table.set_implicit(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let needs_profile_table = !profiles_table.contains_key(profile_name)
|
|
||||||
|| profiles_table
|
|
||||||
.get(profile_name)
|
|
||||||
.and_then(|item| item.as_table())
|
|
||||||
.is_none();
|
|
||||||
if needs_profile_table {
|
|
||||||
profiles_table.insert(profile_name, toml_edit::table());
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(profile_table) = profiles_table
|
|
||||||
.get_mut(profile_name)
|
|
||||||
.and_then(|item| item.as_table_mut())
|
|
||||||
else {
|
|
||||||
return Err(anyhow::anyhow!(format!(
|
|
||||||
"profile table missing for {profile_name}"
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
|
|
||||||
profile_table.set_implicit(false);
|
|
||||||
Ok(profile_table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(jif) refactor config persistence.
|
|
||||||
pub async fn persist_model_selection(
|
|
||||||
codex_home: &Path,
|
|
||||||
active_profile: Option<&str>,
|
|
||||||
model: &str,
|
|
||||||
effort: Option<ReasoningEffort>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
|
||||||
let serialized = match tokio::fs::read_to_string(&config_path).await {
|
|
||||||
Ok(contents) => contents,
|
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => String::new(),
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut doc = if serialized.is_empty() {
|
|
||||||
DocumentMut::new()
|
|
||||||
} else {
|
|
||||||
serialized.parse::<DocumentMut>()?
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(profile_name) = active_profile {
|
|
||||||
let profile_table = ensure_profile_table(&mut doc, profile_name)?;
|
|
||||||
profile_table["model"] = toml_edit::value(model);
|
|
||||||
match effort {
|
|
||||||
Some(effort) => {
|
|
||||||
profile_table["model_reasoning_effort"] = toml_edit::value(effort.to_string());
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
profile_table.remove("model_reasoning_effort");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let table = doc.as_table_mut();
|
|
||||||
table["model"] = toml_edit::value(model);
|
|
||||||
match effort {
|
|
||||||
Some(effort) => {
|
|
||||||
table["model_reasoning_effort"] = toml_edit::value(effort.to_string());
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
table.remove("model_reasoning_effort");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(jif) refactor the home creation
|
|
||||||
tokio::fs::create_dir_all(codex_home)
|
|
||||||
.await
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to create Codex home directory at {}",
|
|
||||||
codex_home.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
tokio::fs::write(&config_path, doc.to_string())
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("failed to persist config.toml at {}", config_path.display()))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a single dotted-path override onto a TOML value.
|
/// Apply a single dotted-path override onto a TOML value.
|
||||||
@@ -1579,7 +1244,11 @@ pub fn log_dir(cfg: &Config) -> std::io::Result<PathBuf> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::config_edit::ConfigEdit;
|
||||||
|
use crate::config_edit::ConfigEditsBuilder;
|
||||||
|
use crate::config_edit::apply_blocking;
|
||||||
use crate::config_types::HistoryPersistence;
|
use crate::config_types::HistoryPersistence;
|
||||||
|
use crate::config_types::McpServerTransportConfig;
|
||||||
use crate::config_types::Notifications;
|
use crate::config_types::Notifications;
|
||||||
use crate::features::Feature;
|
use crate::features::Feature;
|
||||||
|
|
||||||
@@ -2107,7 +1776,7 @@ trust_level = "trusted"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
|
async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let mut servers = BTreeMap::new();
|
let mut servers = BTreeMap::new();
|
||||||
@@ -2129,7 +1798,11 @@ trust_level = "trusted"
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
||||||
assert_eq!(loaded.len(), 1);
|
assert_eq!(loaded.len(), 1);
|
||||||
@@ -2155,7 +1828,11 @@ trust_level = "trusted"
|
|||||||
assert!(docs.enabled);
|
assert!(docs.enabled);
|
||||||
|
|
||||||
let empty = BTreeMap::new();
|
let empty = BTreeMap::new();
|
||||||
write_global_mcp_servers(codex_home.path(), &empty)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(empty.clone())],
|
||||||
|
)?;
|
||||||
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
||||||
assert!(loaded.is_empty());
|
assert!(loaded.is_empty());
|
||||||
|
|
||||||
@@ -2243,7 +1920,7 @@ bearer_token = "secret"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {
|
async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
@@ -2267,7 +1944,11 @@ bearer_token = "secret"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2310,7 +1991,7 @@ ZIG_VAR = "3"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
@@ -2331,7 +2012,11 @@ ZIG_VAR = "3"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2353,7 +2038,7 @@ ZIG_VAR = "3"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
|
async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let cwd_path = PathBuf::from("/tmp/codex-mcp");
|
let cwd_path = PathBuf::from("/tmp/codex-mcp");
|
||||||
@@ -2375,7 +2060,11 @@ ZIG_VAR = "3"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2397,8 +2086,7 @@ ZIG_VAR = "3"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow::Result<()>
|
async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow::Result<()> {
|
||||||
{
|
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
@@ -2418,7 +2106,11 @@ ZIG_VAR = "3"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2453,8 +2145,7 @@ startup_timeout_sec = 2.0
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_streamable_http_serializes_custom_headers()
|
async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyhow::Result<()> {
|
||||||
-> anyhow::Result<()> {
|
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
@@ -2476,7 +2167,11 @@ startup_timeout_sec = 2.0
|
|||||||
disabled_tools: None,
|
disabled_tools: None,
|
||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2522,8 +2217,7 @@ X-Auth = "DOCS_AUTH"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_streamable_http_removes_optional_sections()
|
async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyhow::Result<()> {
|
||||||
-> anyhow::Result<()> {
|
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
@@ -2548,7 +2242,11 @@ X-Auth = "DOCS_AUTH"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
let serialized_with_optional = std::fs::read_to_string(&config_path)?;
|
let serialized_with_optional = std::fs::read_to_string(&config_path)?;
|
||||||
assert!(serialized_with_optional.contains("bearer_token_env_var = \"MCP_TOKEN\""));
|
assert!(serialized_with_optional.contains("bearer_token_env_var = \"MCP_TOKEN\""));
|
||||||
assert!(serialized_with_optional.contains("[mcp_servers.docs.http_headers]"));
|
assert!(serialized_with_optional.contains("[mcp_servers.docs.http_headers]"));
|
||||||
@@ -2570,7 +2268,11 @@ X-Auth = "DOCS_AUTH"
|
|||||||
disabled_tools: None,
|
disabled_tools: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2603,7 +2305,7 @@ url = "https://example.com/mcp"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_streamable_http_isolates_headers_between_servers()
|
async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
|
||||||
-> anyhow::Result<()> {
|
-> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
@@ -2650,7 +2352,11 @@ url = "https://example.com/mcp"
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
assert!(
|
assert!(
|
||||||
@@ -2704,7 +2410,7 @@ url = "https://example.com/mcp"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {
|
async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
@@ -2725,7 +2431,11 @@ url = "https://example.com/mcp"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2742,7 +2452,7 @@ url = "https://example.com/mcp"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_global_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
|
async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
@@ -2763,7 +2473,11 @@ url = "https://example.com/mcp"
|
|||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
apply_blocking(
|
||||||
|
codex_home.path(),
|
||||||
|
None,
|
||||||
|
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||||
|
)?;
|
||||||
|
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
let serialized = std::fs::read_to_string(&config_path)?;
|
let serialized = std::fs::read_to_string(&config_path)?;
|
||||||
@@ -2785,16 +2499,13 @@ url = "https://example.com/mcp"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn persist_model_selection_updates_defaults() -> anyhow::Result<()> {
|
async fn set_model_updates_defaults() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
persist_model_selection(
|
ConfigEditsBuilder::new(codex_home.path())
|
||||||
codex_home.path(),
|
.set_model(Some("gpt-5-codex"), Some(ReasoningEffort::High))
|
||||||
None,
|
.apply()
|
||||||
"gpt-5-codex",
|
.await?;
|
||||||
Some(ReasoningEffort::High),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let serialized =
|
let serialized =
|
||||||
tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;
|
tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;
|
||||||
@@ -2807,7 +2518,7 @@ url = "https://example.com/mcp"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn persist_model_selection_overwrites_existing_model() -> anyhow::Result<()> {
|
async fn set_model_overwrites_existing_model() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
|
|
||||||
@@ -2823,13 +2534,10 @@ model = "gpt-4.1"
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
persist_model_selection(
|
ConfigEditsBuilder::new(codex_home.path())
|
||||||
codex_home.path(),
|
.set_model(Some("o4-mini"), Some(ReasoningEffort::High))
|
||||||
None,
|
.apply()
|
||||||
"o4-mini",
|
.await?;
|
||||||
Some(ReasoningEffort::High),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let serialized = tokio::fs::read_to_string(config_path).await?;
|
let serialized = tokio::fs::read_to_string(config_path).await?;
|
||||||
let parsed: ConfigToml = toml::from_str(&serialized)?;
|
let parsed: ConfigToml = toml::from_str(&serialized)?;
|
||||||
@@ -2848,16 +2556,14 @@ model = "gpt-4.1"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn persist_model_selection_updates_profile() -> anyhow::Result<()> {
|
async fn set_model_updates_profile() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|
||||||
persist_model_selection(
|
ConfigEditsBuilder::new(codex_home.path())
|
||||||
codex_home.path(),
|
.with_profile(Some("dev"))
|
||||||
Some("dev"),
|
.set_model(Some("gpt-5-codex"), Some(ReasoningEffort::Medium))
|
||||||
"gpt-5-codex",
|
.apply()
|
||||||
Some(ReasoningEffort::Medium),
|
.await?;
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let serialized =
|
let serialized =
|
||||||
tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;
|
tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;
|
||||||
@@ -2877,7 +2583,7 @@ model = "gpt-4.1"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn persist_model_selection_updates_existing_profile() -> anyhow::Result<()> {
|
async fn set_model_updates_existing_profile() -> anyhow::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||||
|
|
||||||
@@ -2894,13 +2600,11 @@ model = "gpt-5-codex"
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
persist_model_selection(
|
ConfigEditsBuilder::new(codex_home.path())
|
||||||
codex_home.path(),
|
.with_profile(Some("dev"))
|
||||||
Some("dev"),
|
.set_model(Some("o4-high"), Some(ReasoningEffort::Medium))
|
||||||
"o4-high",
|
.apply()
|
||||||
Some(ReasoningEffort::Medium),
|
.await?;
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let serialized = tokio::fs::read_to_string(config_path).await?;
|
let serialized = tokio::fs::read_to_string(config_path).await?;
|
||||||
let parsed: ConfigToml = toml::from_str(&serialized)?;
|
let parsed: ConfigToml = toml::from_str(&serialized)?;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -361,7 +361,7 @@ pub struct Notice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Notice {
|
impl Notice {
|
||||||
/// used by set_hide_full_access_warning until we refactor config updates
|
/// referenced by config_edit helpers when writing notice flags
|
||||||
pub(crate) const TABLE_KEY: &'static str = "notice";
|
pub(crate) const TABLE_KEY: &'static str = "notice";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ use codex_ansi_escape::ansi_escape_line;
|
|||||||
use codex_core::AuthManager;
|
use codex_core::AuthManager;
|
||||||
use codex_core::ConversationManager;
|
use codex_core::ConversationManager;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::persist_model_selection;
|
use codex_core::config_edit::ConfigEditsBuilder;
|
||||||
use codex_core::config::set_hide_full_access_warning;
|
|
||||||
use codex_core::model_family::find_family_for_model;
|
use codex_core::model_family::find_family_for_model;
|
||||||
use codex_core::protocol::SessionSource;
|
use codex_core::protocol::SessionSource;
|
||||||
use codex_core::protocol::TokenUsage;
|
use codex_core::protocol::TokenUsage;
|
||||||
@@ -374,7 +373,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
AppEvent::PersistModelSelection { model, effort } => {
|
AppEvent::PersistModelSelection { model, effort } => {
|
||||||
let profile = self.active_profile.as_deref();
|
let profile = self.active_profile.as_deref();
|
||||||
match persist_model_selection(&self.config.codex_home, profile, &model, effort)
|
match ConfigEditsBuilder::new(&self.config.codex_home)
|
||||||
|
.with_profile(profile)
|
||||||
|
.set_model(Some(model.as_str()), effort)
|
||||||
|
.apply()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
@@ -421,7 +423,11 @@ impl App {
|
|||||||
self.chat_widget.set_full_access_warning_acknowledged(ack);
|
self.chat_widget.set_full_access_warning_acknowledged(ack);
|
||||||
}
|
}
|
||||||
AppEvent::PersistFullAccessWarningAcknowledged => {
|
AppEvent::PersistFullAccessWarningAcknowledged => {
|
||||||
if let Err(err) = set_hide_full_access_warning(&self.config.codex_home, true) {
|
if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home)
|
||||||
|
.set_hide_full_access_warning(true)
|
||||||
|
.apply()
|
||||||
|
.await
|
||||||
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
error = %err,
|
error = %err,
|
||||||
"failed to persist full access warning acknowledgement"
|
"failed to persist full access warning acknowledgement"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use codex_core::config::set_windows_wsl_setup_acknowledged;
|
use codex_core::config_edit::ConfigEditsBuilder;
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use crossterm::event::KeyEventKind;
|
use crossterm::event::KeyEventKind;
|
||||||
@@ -66,7 +66,10 @@ impl WindowsSetupWidget {
|
|||||||
|
|
||||||
fn handle_continue(&mut self) {
|
fn handle_continue(&mut self) {
|
||||||
self.highlighted = WindowsSetupSelection::Continue;
|
self.highlighted = WindowsSetupSelection::Continue;
|
||||||
match set_windows_wsl_setup_acknowledged(&self.codex_home, true) {
|
match ConfigEditsBuilder::new(&self.codex_home)
|
||||||
|
.set_windows_wsl_setup_acknowledged(true)
|
||||||
|
.apply_blocking()
|
||||||
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.selection = Some(WindowsSetupSelection::Continue);
|
self.selection = Some(WindowsSetupSelection::Continue);
|
||||||
self.exit_requested = false;
|
self.exit_requested = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user