mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
Merge 8153d12590 into sapling-pr-archive-bolinfest
This commit is contained in:
Generated
+1
@@ -1483,6 +1483,7 @@
|
||||
"serde_derive_1.0.228": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0.81\"}],\"features\":{\"default\":[],\"deserialize_in_place\":[]}}",
|
||||
"serde_derive_internals_0.29.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}",
|
||||
"serde_html_form_0.3.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches2\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"divan\",\"req\":\"^0.1.11\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"form_urlencoded\",\"req\":\"^1.0.1\"},{\"default_features\":false,\"name\":\"indexmap\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.45.0\"},{\"name\":\"itoa\",\"req\":\"^1.0.1\"},{\"name\":\"ryu\",\"optional\":true,\"req\":\"^1.0.9\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"}],\"features\":{\"default\":[\"ryu\",\"std\"],\"std\":[]}}",
|
||||
"serde_ignored_0.1.14": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.110\"}],\"features\":{}}",
|
||||
"serde_json_1.0.149": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.11\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.2.3\"},{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2.0.2\"},{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11.10\"},{\"default_features\":false,\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_stacker\",\"req\":\"^0.1.8\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"},{\"name\":\"zmij\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[\"serde_core/alloc\"],\"arbitrary_precision\":[],\"default\":[\"std\"],\"float_roundtrip\":[],\"preserve_order\":[\"indexmap\",\"std\"],\"raw_value\":[],\"std\":[\"memchr/std\",\"serde_core/std\"],\"unbounded_depth\":[]}}",
|
||||
"serde_path_to_error_0.1.20": "{\"dependencies\":[{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.100\"}],\"features\":{}}",
|
||||
"serde_repr_0.1.20": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.100\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.81\"}],\"features\":{}}",
|
||||
|
||||
Generated
+12
-1
@@ -2419,6 +2419,7 @@ dependencies = [
|
||||
"prost 0.14.3",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_ignored",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2",
|
||||
@@ -5437,7 +5438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11624,6 +11625,16 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_ignored"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
|
||||
@@ -352,6 +352,7 @@ seccompiler = "0.5.0"
|
||||
semver = "1.0"
|
||||
sentry = "0.46.0"
|
||||
serde = "1"
|
||||
serde_ignored = "0.1.14"
|
||||
serde_json = "1"
|
||||
serde_path_to_error = "0.1.20"
|
||||
serde_with = "3.17"
|
||||
|
||||
@@ -334,6 +334,8 @@ pub struct InProcessClientStartArgs {
|
||||
pub cli_overrides: Vec<(String, TomlValue)>,
|
||||
/// Loader override knobs used by config API paths.
|
||||
pub loader_overrides: LoaderOverrides,
|
||||
/// Whether config API paths should reject unknown config fields.
|
||||
pub strict_config: bool,
|
||||
/// Preloaded cloud requirements provider.
|
||||
pub cloud_requirements: CloudRequirementsLoader,
|
||||
/// Feedback sink used by app-server/core telemetry and logs.
|
||||
@@ -400,6 +402,7 @@ impl InProcessClientStartArgs {
|
||||
config: self.config,
|
||||
cli_overrides: self.cli_overrides,
|
||||
loader_overrides: self.loader_overrides,
|
||||
strict_config: self.strict_config,
|
||||
cloud_requirements: self.cloud_requirements,
|
||||
thread_config_loader,
|
||||
feedback: self.feedback,
|
||||
@@ -1025,6 +1028,7 @@ mod tests {
|
||||
config,
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
@@ -2109,6 +2113,7 @@ mod tests {
|
||||
config: config.clone(),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
@@ -2149,6 +2154,7 @@ mod tests {
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(crate) struct ConfigManager {
|
||||
cli_overrides: Arc<RwLock<Vec<(String, TomlValue)>>>,
|
||||
runtime_feature_enablement: Arc<RwLock<BTreeMap<String, bool>>>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>,
|
||||
@@ -40,6 +41,7 @@ impl ConfigManager {
|
||||
codex_home: PathBuf,
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
@@ -49,6 +51,7 @@ impl ConfigManager {
|
||||
cli_overrides: Arc::new(RwLock::new(cli_overrides)),
|
||||
runtime_feature_enablement: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements: Arc::new(RwLock::new(cloud_requirements)),
|
||||
arg0_paths,
|
||||
thread_config_loader: Arc::new(RwLock::new(thread_config_loader)),
|
||||
@@ -217,6 +220,7 @@ impl ConfigManager {
|
||||
.codex_home(self.codex_home.clone())
|
||||
.cli_overrides(merged_cli_overrides)
|
||||
.loader_overrides(self.loader_overrides.clone())
|
||||
.strict_config(self.strict_config)
|
||||
.harness_overrides(typesafe_overrides)
|
||||
.fallback_cwd(fallback_cwd)
|
||||
.cloud_requirements(self.current_cloud_requirements())
|
||||
@@ -245,7 +249,10 @@ impl ConfigManager {
|
||||
&self.codex_home,
|
||||
cwd,
|
||||
&self.current_cli_overrides(),
|
||||
self.loader_overrides.clone(),
|
||||
codex_config::ConfigLoadOptions {
|
||||
loader_overrides: self.loader_overrides.clone(),
|
||||
strict_config: self.strict_config,
|
||||
},
|
||||
self.current_cloud_requirements(),
|
||||
thread_config_loader.as_ref(),
|
||||
)
|
||||
@@ -280,6 +287,7 @@ impl ConfigManager {
|
||||
codex_home,
|
||||
cli_overrides,
|
||||
loader_overrides,
|
||||
/*strict_config*/ false,
|
||||
cloud_requirements,
|
||||
Arg0DispatchPaths::default(),
|
||||
Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
|
||||
@@ -119,6 +119,8 @@ pub struct InProcessStartArgs {
|
||||
pub cli_overrides: Vec<(String, TomlValue)>,
|
||||
/// Loader override knobs used by config API paths.
|
||||
pub loader_overrides: LoaderOverrides,
|
||||
/// Whether config API paths should reject unknown config fields.
|
||||
pub strict_config: bool,
|
||||
/// Preloaded cloud requirements provider.
|
||||
pub cloud_requirements: CloudRequirementsLoader,
|
||||
/// Loader used to fetch typed thread config sources before a thread starts.
|
||||
@@ -418,6 +420,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
|
||||
args.config.codex_home.to_path_buf(),
|
||||
args.cli_overrides,
|
||||
args.loader_overrides,
|
||||
args.strict_config,
|
||||
args.cloud_requirements,
|
||||
args.arg0_paths.clone(),
|
||||
args.thread_config_loader,
|
||||
@@ -772,6 +775,7 @@ mod tests {
|
||||
config,
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
|
||||
@@ -370,12 +370,14 @@ pub async fn run_main(
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
default_analytics_enabled: bool,
|
||||
) -> IoResult<()> {
|
||||
run_main_with_transport(
|
||||
arg0_paths,
|
||||
cli_config_overrides,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
default_analytics_enabled,
|
||||
AppServerTransport::Stdio,
|
||||
SessionSource::VSCode,
|
||||
@@ -406,6 +408,7 @@ pub async fn run_main_with_transport(
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
default_analytics_enabled: bool,
|
||||
transport: AppServerTransport,
|
||||
session_source: SessionSource,
|
||||
@@ -414,6 +417,7 @@ pub async fn run_main_with_transport(
|
||||
arg0_paths,
|
||||
cli_config_overrides,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
default_analytics_enabled,
|
||||
transport,
|
||||
session_source,
|
||||
@@ -427,6 +431,7 @@ pub async fn run_main_with_transport_options(
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
default_analytics_enabled: bool,
|
||||
transport: AppServerTransport,
|
||||
session_source: SessionSource,
|
||||
@@ -462,6 +467,7 @@ pub async fn run_main_with_transport_options(
|
||||
codex_home.to_path_buf(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
Default::default(),
|
||||
arg0_paths.clone(),
|
||||
Arc::new(NoopThreadConfigLoader),
|
||||
|
||||
@@ -64,6 +64,7 @@ fn main() -> anyhow::Result<()> {
|
||||
arg0_paths,
|
||||
CliConfigOverrides::default(),
|
||||
loader_overrides,
|
||||
/*strict_config*/ false,
|
||||
/*default_analytics_enabled*/ false,
|
||||
transport,
|
||||
session_source,
|
||||
|
||||
@@ -207,6 +207,7 @@ mod tests {
|
||||
temp_dir.path().to_path_buf(),
|
||||
Vec::new(),
|
||||
LoaderOverrides::without_managed_config_for_tests(),
|
||||
/*strict_config*/ false,
|
||||
CloudRequirementsLoader::default(),
|
||||
Arg0DispatchPaths::default(),
|
||||
loader.clone(),
|
||||
|
||||
@@ -239,6 +239,7 @@ async fn build_test_processor(
|
||||
config.codex_home.to_path_buf(),
|
||||
Vec::new(),
|
||||
LoaderOverrides::default(),
|
||||
/*strict_config*/ false,
|
||||
CloudRequirementsLoader::default(),
|
||||
Arg0DispatchPaths::default(),
|
||||
Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
|
||||
@@ -677,6 +677,7 @@ mod thread_processor_behavior_tests {
|
||||
temp_dir.path().to_path_buf(),
|
||||
Vec::new(),
|
||||
LoaderOverrides::default(),
|
||||
/*strict_config*/ false,
|
||||
CloudRequirementsLoader::default(),
|
||||
Arg0DispatchPaths::default(),
|
||||
Arc::new(StaticThreadConfigLoader::new(vec![
|
||||
|
||||
@@ -150,6 +150,7 @@ async fn get_conversation_summary_by_thread_id_reads_pathless_store_thread() ->
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
|
||||
@@ -200,6 +200,7 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> {
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
|
||||
@@ -75,6 +75,7 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
|
||||
@@ -375,6 +375,7 @@ async fn thread_turns_list_reads_store_history_without_rollout_path() -> Result<
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
@@ -440,6 +441,7 @@ async fn thread_read_loaded_include_turns_reads_store_history_without_rollout_pa
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
@@ -525,6 +527,7 @@ async fn thread_list_includes_store_thread_without_rollout_path() -> Result<()>
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
|
||||
@@ -246,6 +246,7 @@ async fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_git_utils::ApplyGitRequest;
|
||||
use codex_git_utils::apply_git_patch;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
@@ -23,13 +23,14 @@ pub async fn run_apply_command(
|
||||
apply_cli: ApplyCommand,
|
||||
cwd: Option<PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
let config = Config::load_with_cli_overrides(
|
||||
apply_cli
|
||||
.config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?,
|
||||
)
|
||||
.await?;
|
||||
let cli_overrides = apply_cli
|
||||
.config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_overrides)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let task_response = get_task(&config, apply_cli.task_id).await?;
|
||||
apply_diff_from_task(task_response, cwd).await
|
||||
|
||||
@@ -190,6 +190,7 @@ async fn run_command_under_sandbox(
|
||||
.map_err(anyhow::Error::msg)?,
|
||||
codex_linux_sandbox_exe,
|
||||
config_options,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -636,12 +637,14 @@ async fn load_debug_sandbox_config(
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
options: DebugSandboxConfigOptions,
|
||||
strict_config: bool,
|
||||
) -> anyhow::Result<Config> {
|
||||
load_debug_sandbox_config_with_codex_home(
|
||||
cli_overrides,
|
||||
codex_linux_sandbox_exe,
|
||||
options,
|
||||
/*codex_home*/ None,
|
||||
strict_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -651,6 +654,7 @@ async fn load_debug_sandbox_config_with_codex_home(
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
options: DebugSandboxConfigOptions,
|
||||
codex_home: Option<PathBuf>,
|
||||
strict_config: bool,
|
||||
) -> anyhow::Result<Config> {
|
||||
let DebugSandboxConfigOptions {
|
||||
permissions_profile,
|
||||
@@ -680,6 +684,7 @@ async fn load_debug_sandbox_config_with_codex_home(
|
||||
},
|
||||
codex_home.clone(),
|
||||
managed_requirements_mode,
|
||||
strict_config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -697,6 +702,7 @@ async fn load_debug_sandbox_config_with_codex_home(
|
||||
},
|
||||
codex_home,
|
||||
managed_requirements_mode,
|
||||
strict_config,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
@@ -707,14 +713,16 @@ async fn build_debug_sandbox_config(
|
||||
harness_overrides: ConfigOverrides,
|
||||
codex_home: Option<PathBuf>,
|
||||
managed_requirements_mode: ManagedRequirementsMode,
|
||||
strict_config: bool,
|
||||
) -> std::io::Result<Config> {
|
||||
let mut builder = ConfigBuilder::default()
|
||||
.cli_overrides(cli_overrides)
|
||||
.harness_overrides(harness_overrides);
|
||||
if let ManagedRequirementsMode::Ignore = managed_requirements_mode {
|
||||
.harness_overrides(harness_overrides)
|
||||
.strict_config(strict_config);
|
||||
if matches!(managed_requirements_mode, ManagedRequirementsMode::Ignore) {
|
||||
builder = builder.loader_overrides(LoaderOverrides {
|
||||
ignore_managed_requirements: true,
|
||||
..Default::default()
|
||||
..LoaderOverrides::default()
|
||||
});
|
||||
}
|
||||
if let Some(codex_home) = codex_home {
|
||||
@@ -793,6 +801,7 @@ mod tests {
|
||||
ConfigOverrides::default(),
|
||||
Some(codex_home_path.clone()),
|
||||
ManagedRequirementsMode::Include,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
let legacy_config = build_debug_sandbox_config(
|
||||
@@ -803,6 +812,7 @@ mod tests {
|
||||
},
|
||||
Some(codex_home_path.clone()),
|
||||
ManagedRequirementsMode::Include,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -815,6 +825,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Include,
|
||||
},
|
||||
Some(codex_home_path),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -850,6 +861,7 @@ mod tests {
|
||||
ConfigOverrides::default(),
|
||||
Some(codex_home_path.clone()),
|
||||
ManagedRequirementsMode::Include,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
let read_only_config = build_debug_sandbox_config(
|
||||
@@ -860,6 +872,7 @@ mod tests {
|
||||
},
|
||||
Some(codex_home_path.clone()),
|
||||
ManagedRequirementsMode::Include,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -872,6 +885,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Include,
|
||||
},
|
||||
Some(codex_home_path),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -915,6 +929,7 @@ mod tests {
|
||||
},
|
||||
Some(codex_home_path.clone()),
|
||||
ManagedRequirementsMode::Include,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -927,6 +942,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Include,
|
||||
},
|
||||
Some(codex_home_path),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -952,6 +968,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Ignore,
|
||||
},
|
||||
Some(codex_home.path().to_path_buf()),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -984,6 +1001,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Ignore,
|
||||
},
|
||||
Some(codex_home.path().to_path_buf()),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1012,6 +1030,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Ignore,
|
||||
},
|
||||
Some(codex_home.path().to_path_buf()),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1023,6 +1042,7 @@ mod tests {
|
||||
ConfigOverrides::default(),
|
||||
Some(codex_home.path().to_path_buf()),
|
||||
ManagedRequirementsMode::Include,
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1048,6 +1068,7 @@ mod tests {
|
||||
managed_requirements_mode: ManagedRequirementsMode::Ignore,
|
||||
},
|
||||
Some(codex_home.path().to_path_buf()),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_login::CLIENT_ID;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::ServerOptions;
|
||||
@@ -431,7 +432,11 @@ async fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config
|
||||
}
|
||||
};
|
||||
|
||||
match Config::load_with_cli_overrides(cli_overrides).await {
|
||||
match ConfigBuilder::default()
|
||||
.cli_overrides(cli_overrides)
|
||||
.build()
|
||||
.await
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Error loading configuration: {e}");
|
||||
|
||||
+253
-22
@@ -55,7 +55,7 @@ use crate::marketplace_cmd::MarketplaceCli;
|
||||
use crate::mcp_cmd::McpCli;
|
||||
|
||||
use codex_core::build_models_manager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
@@ -109,7 +109,7 @@ enum Subcommand {
|
||||
Exec(ExecCli),
|
||||
|
||||
/// Run a code review non-interactively.
|
||||
Review(ReviewArgs),
|
||||
Review(ReviewCommand),
|
||||
|
||||
/// Manage login.
|
||||
Login(LoginCommand),
|
||||
@@ -124,7 +124,7 @@ enum Subcommand {
|
||||
Plugin(PluginCli),
|
||||
|
||||
/// Start Codex as an MCP server (stdio).
|
||||
McpServer,
|
||||
McpServer(McpServerCommand),
|
||||
|
||||
/// [experimental] Run the app server or related tooling.
|
||||
AppServer(AppServerCommand),
|
||||
@@ -266,6 +266,23 @@ struct DebugModelsCommand {
|
||||
bundled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct ReviewCommand {
|
||||
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
|
||||
#[arg(long = "strict-config", default_value_t = false)]
|
||||
strict_config: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
args: ReviewArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct McpServerCommand {
|
||||
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
|
||||
#[arg(long = "strict-config", default_value_t = false)]
|
||||
strict_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct DebugTraceReduceCommand {
|
||||
/// Trace bundle directory containing manifest.json and trace.jsonl.
|
||||
@@ -419,6 +436,10 @@ struct AppServerCommand {
|
||||
#[command(subcommand)]
|
||||
subcommand: Option<AppServerSubcommand>,
|
||||
|
||||
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
|
||||
#[arg(long = "strict-config", default_value_t = false)]
|
||||
strict_config: bool,
|
||||
|
||||
/// Transport endpoint URL. Supported values: `stdio://` (default),
|
||||
/// `unix://`, `unix://PATH`, `off`.
|
||||
#[arg(
|
||||
@@ -803,6 +824,8 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
root_config_overrides.raw_overrides.extend(toggle_overrides);
|
||||
let root_remote = remote.remote;
|
||||
let root_remote_auth_token_env = remote.remote_auth_token_env;
|
||||
let root_strict_config = interactive.strict_config;
|
||||
reject_root_strict_config_for_subcommand(root_strict_config, &subcommand)?;
|
||||
|
||||
match subcommand {
|
||||
None => {
|
||||
@@ -828,13 +851,17 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
exec_cli
|
||||
.shared
|
||||
.inherit_exec_root_options(&interactive.shared);
|
||||
exec_cli.strict_config |= root_strict_config;
|
||||
prepend_config_flags(
|
||||
&mut exec_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
codex_exec::run_main(exec_cli, arg0_paths.clone()).await?;
|
||||
}
|
||||
Some(Subcommand::Review(review_args)) => {
|
||||
Some(Subcommand::Review(ReviewCommand {
|
||||
strict_config,
|
||||
args: review_args,
|
||||
})) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
@@ -842,19 +869,25 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
)?;
|
||||
let mut exec_cli = ExecCli::try_parse_from(["codex", "exec"])?;
|
||||
exec_cli.command = Some(ExecCommand::Review(review_args));
|
||||
exec_cli.strict_config = strict_config || root_strict_config;
|
||||
prepend_config_flags(
|
||||
&mut exec_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
);
|
||||
codex_exec::run_main(exec_cli, arg0_paths.clone()).await?;
|
||||
}
|
||||
Some(Subcommand::McpServer) => {
|
||||
Some(Subcommand::McpServer(McpServerCommand { strict_config })) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"mcp-server",
|
||||
)?;
|
||||
codex_mcp_server::run_main(arg0_paths.clone(), root_config_overrides).await?;
|
||||
codex_mcp_server::run_main(
|
||||
arg0_paths.clone(),
|
||||
root_config_overrides,
|
||||
strict_config || root_strict_config,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Some(Subcommand::Mcp(mut mcp_cli)) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
@@ -887,9 +920,12 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
Some(Subcommand::AppServer(app_server_cli)) => {
|
||||
let AppServerCommand {
|
||||
subcommand,
|
||||
strict_config: app_server_strict_config,
|
||||
listen,
|
||||
analytics_default_enabled,
|
||||
} = app_server_cli;
|
||||
let strict_config = app_server_strict_config || root_strict_config;
|
||||
reject_strict_config_for_app_server_subcommand(strict_config, subcommand.as_ref())?;
|
||||
reject_remote_mode_for_app_server_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
@@ -902,6 +938,7 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
arg0_paths.clone(),
|
||||
root_config_overrides,
|
||||
codex_config::LoaderOverrides::default(),
|
||||
strict_config,
|
||||
analytics_default_enabled,
|
||||
transport,
|
||||
codex_protocol::protocol::SessionSource::VSCode,
|
||||
@@ -1304,11 +1341,11 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = Config::load_with_cli_overrides_and_harness_overrides(
|
||||
cli_kv_overrides,
|
||||
overrides,
|
||||
)
|
||||
.await?;
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_kv_overrides)
|
||||
.harness_overrides(overrides)
|
||||
.build()
|
||||
.await?;
|
||||
let mut rows = Vec::with_capacity(FEATURES.len());
|
||||
let mut name_width = 0;
|
||||
let mut stage_width = 0;
|
||||
@@ -1483,8 +1520,11 @@ async fn run_debug_prompt_input_command(
|
||||
additional_writable_roots: shared.add_dir,
|
||||
..Default::default()
|
||||
};
|
||||
let config =
|
||||
Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?;
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_kv_overrides)
|
||||
.harness_overrides(overrides)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let mut input = shared
|
||||
.images
|
||||
@@ -1515,7 +1555,10 @@ async fn run_debug_models_command(
|
||||
let cli_overrides = root_config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(cli_overrides).await?;
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_overrides)
|
||||
.build()
|
||||
.await?;
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
|
||||
let models_manager = build_models_manager(&config, auth_manager);
|
||||
@@ -1540,8 +1583,11 @@ async fn run_debug_clear_memories_command(
|
||||
config_profile: interactive.config_profile.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let config =
|
||||
Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?;
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_kv_overrides)
|
||||
.harness_overrides(overrides)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let state_path = state_db_path(config.sqlite_home.as_path());
|
||||
let mut cleared_state_db = false;
|
||||
@@ -1576,9 +1622,7 @@ fn prepend_config_flags(
|
||||
subcommand_config_overrides: &mut CliConfigOverrides,
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
) {
|
||||
subcommand_config_overrides
|
||||
.raw_overrides
|
||||
.splice(0..0, cli_config_overrides.raw_overrides);
|
||||
subcommand_config_overrides.prepend_root_overrides(cli_config_overrides);
|
||||
}
|
||||
|
||||
fn reject_remote_mode_for_subcommand(
|
||||
@@ -1599,12 +1643,89 @@ fn reject_remote_mode_for_subcommand(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reject_root_strict_config_for_subcommand(
|
||||
strict_config: bool,
|
||||
subcommand: &Option<Subcommand>,
|
||||
) -> anyhow::Result<()> {
|
||||
if !strict_config {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(subcommand_name) = unsupported_subcommand_name_for_strict_config(subcommand) else {
|
||||
return Ok(());
|
||||
};
|
||||
reject_strict_config_for_unsupported_subcommand(strict_config, subcommand_name)
|
||||
}
|
||||
|
||||
fn unsupported_subcommand_name_for_strict_config(
|
||||
subcommand: &Option<Subcommand>,
|
||||
) -> Option<&'static str> {
|
||||
match subcommand {
|
||||
None
|
||||
| Some(Subcommand::Exec(_))
|
||||
| Some(Subcommand::Review(_))
|
||||
| Some(Subcommand::McpServer(_))
|
||||
| Some(Subcommand::Resume(_))
|
||||
| Some(Subcommand::Fork(_)) => None,
|
||||
Some(Subcommand::AppServer(app_server)) if app_server.subcommand.is_none() => None,
|
||||
Some(Subcommand::AppServer(app_server)) => {
|
||||
Some(app_server_subcommand_name(app_server.subcommand.as_ref()))
|
||||
}
|
||||
Some(Subcommand::RemoteControl) => Some("remote-control"),
|
||||
Some(Subcommand::Mcp(_)) => Some("mcp"),
|
||||
Some(Subcommand::Plugin(_)) => Some("plugin"),
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
Some(Subcommand::App(_)) => Some("app"),
|
||||
Some(Subcommand::Login(_)) => Some("login"),
|
||||
Some(Subcommand::Logout(_)) => Some("logout"),
|
||||
Some(Subcommand::Completion(_)) => Some("completion"),
|
||||
Some(Subcommand::Update) => Some("update"),
|
||||
Some(Subcommand::Cloud(_)) => Some("cloud"),
|
||||
Some(Subcommand::Sandbox(_)) => Some("sandbox"),
|
||||
Some(Subcommand::Debug(_)) => Some("debug"),
|
||||
Some(Subcommand::Execpolicy(_)) => Some("execpolicy"),
|
||||
Some(Subcommand::Apply(_)) => Some("apply"),
|
||||
Some(Subcommand::ResponsesApiProxy(_)) => Some("responses-api-proxy"),
|
||||
Some(Subcommand::StdioToUds(_)) => Some("stdio-to-uds"),
|
||||
Some(Subcommand::ExecServer(_)) => Some("exec-server"),
|
||||
Some(Subcommand::Features(_)) => Some("features"),
|
||||
}
|
||||
}
|
||||
|
||||
fn reject_strict_config_for_app_server_subcommand(
|
||||
strict_config: bool,
|
||||
subcommand: Option<&AppServerSubcommand>,
|
||||
) -> anyhow::Result<()> {
|
||||
if subcommand.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
reject_strict_config_for_unsupported_subcommand(
|
||||
strict_config,
|
||||
app_server_subcommand_name(subcommand),
|
||||
)
|
||||
}
|
||||
|
||||
fn reject_strict_config_for_unsupported_subcommand(
|
||||
strict_config: bool,
|
||||
subcommand: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
if strict_config {
|
||||
anyhow::bail!("`--strict-config` is not supported for `codex {subcommand}`");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reject_remote_mode_for_app_server_subcommand(
|
||||
remote: Option<&str>,
|
||||
remote_auth_token_env: Option<&str>,
|
||||
subcommand: Option<&AppServerSubcommand>,
|
||||
) -> anyhow::Result<()> {
|
||||
let subcommand_name = match subcommand {
|
||||
let subcommand_name = app_server_subcommand_name(subcommand);
|
||||
reject_remote_mode_for_subcommand(remote, remote_auth_token_env, subcommand_name)
|
||||
}
|
||||
|
||||
fn app_server_subcommand_name(subcommand: Option<&AppServerSubcommand>) -> &'static str {
|
||||
match subcommand {
|
||||
None => "app-server",
|
||||
Some(AppServerSubcommand::Daemon(daemon)) => match daemon.subcommand {
|
||||
AppServerDaemonSubcommand::Bootstrap(_) => "app-server daemon bootstrap",
|
||||
@@ -1626,8 +1747,7 @@ fn reject_remote_mode_for_app_server_subcommand(
|
||||
Some(AppServerSubcommand::GenerateInternalJsonSchema(_)) => {
|
||||
"app-server generate-internal-json-schema"
|
||||
}
|
||||
};
|
||||
reject_remote_mode_for_subcommand(remote, remote_auth_token_env, subcommand_name)
|
||||
}
|
||||
}
|
||||
|
||||
async fn print_app_server_daemon_output(command: AppServerLifecycleCommand) -> anyhow::Result<()> {
|
||||
@@ -1787,6 +1907,7 @@ fn finalize_fork_interactive(
|
||||
fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli) {
|
||||
let TuiCli {
|
||||
shared,
|
||||
strict_config,
|
||||
approval_policy,
|
||||
web_search,
|
||||
prompt,
|
||||
@@ -1802,6 +1923,9 @@ fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli)
|
||||
if web_search {
|
||||
interactive.web_search = true;
|
||||
}
|
||||
if strict_config {
|
||||
interactive.strict_config = true;
|
||||
}
|
||||
if let Some(prompt) = prompt {
|
||||
// Normalize CRLF/CR to LF so CLI-provided text can't leak `\r` into TUI state.
|
||||
interactive.prompt = Some(prompt.replace("\r\n", "\n").replace('\r', "\n"));
|
||||
@@ -2289,6 +2413,7 @@ mod tests {
|
||||
"my-profile",
|
||||
"-C",
|
||||
"/tmp",
|
||||
"--strict-config",
|
||||
"-i",
|
||||
"/tmp/a.png,/tmp/b.png",
|
||||
]
|
||||
@@ -2311,6 +2436,7 @@ mod tests {
|
||||
Some(std::path::Path::new("/tmp"))
|
||||
);
|
||||
assert!(interactive.web_search);
|
||||
assert!(interactive.strict_config);
|
||||
let has_a = interactive
|
||||
.images
|
||||
.iter()
|
||||
@@ -2392,6 +2518,77 @@ mod tests {
|
||||
assert!(app_server.analytics_default_enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_parses_for_supported_commands() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--strict-config"]).expect("parse");
|
||||
assert!(cli.interactive.strict_config);
|
||||
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "mcp-server", "--strict-config"])
|
||||
.expect("parse");
|
||||
assert_matches!(
|
||||
cli.subcommand,
|
||||
Some(Subcommand::McpServer(McpServerCommand {
|
||||
strict_config: true,
|
||||
}))
|
||||
);
|
||||
|
||||
let cli =
|
||||
MultitoolCli::try_parse_from(["codex", "review", "--strict-config", "--uncommitted"])
|
||||
.expect("parse");
|
||||
assert_matches!(
|
||||
cli.subcommand,
|
||||
Some(Subcommand::Review(ReviewCommand {
|
||||
strict_config: true,
|
||||
..
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_strict_config_is_rejected_for_unsupported_subcommands() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--strict-config", "mcp", "list"])
|
||||
.expect("parse");
|
||||
let err = reject_root_strict_config_for_subcommand(
|
||||
cli.interactive.strict_config,
|
||||
&cli.subcommand,
|
||||
)
|
||||
.expect_err("mcp should not support root --strict-config");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"`--strict-config` is not supported for `codex mcp`"
|
||||
);
|
||||
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--strict-config", "remote-control"])
|
||||
.expect("parse");
|
||||
let err = reject_root_strict_config_for_subcommand(
|
||||
cli.interactive.strict_config,
|
||||
&cli.subcommand,
|
||||
)
|
||||
.expect_err("remote-control should not support root --strict-config");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"`--strict-config` is not supported for `codex remote-control`"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_subcommands_reject_strict_config() {
|
||||
let app_server =
|
||||
app_server_from_args(["codex", "app-server", "--strict-config", "proxy"].as_ref());
|
||||
let err = reject_strict_config_for_app_server_subcommand(
|
||||
app_server.strict_config,
|
||||
app_server.subcommand.as_ref(),
|
||||
)
|
||||
.expect_err("app-server proxy should not support --strict-config");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"`--strict-config` is not supported for `codex app-server proxy`"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_remote_flag_for_remote_control() {
|
||||
let cli = MultitoolCli::try_parse_from([
|
||||
@@ -2786,4 +2983,38 @@ mod tests {
|
||||
.expect_err("feature should be rejected");
|
||||
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_with_unknown_enable_errors() {
|
||||
let err = strict_config_feature_toggle_error(["--enable", "does_not_exist"].as_ref());
|
||||
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_with_unknown_disable_errors() {
|
||||
let err = strict_config_feature_toggle_error(["--disable", "does_not_exist"].as_ref());
|
||||
assert_eq!(err.to_string(), "Unknown feature flag: does_not_exist");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_with_compound_enable_errors() {
|
||||
let err = strict_config_feature_toggle_error(
|
||||
["--enable", "multi_agent_v2.subagent_usage_hint_text"].as_ref(),
|
||||
);
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Unknown feature flag: multi_agent_v2.subagent_usage_hint_text"
|
||||
);
|
||||
}
|
||||
|
||||
fn strict_config_feature_toggle_error(args: &[&str]) -> anyhow::Error {
|
||||
let cli_args = std::iter::once("codex")
|
||||
.chain(std::iter::once("--strict-config"))
|
||||
.chain(args.iter().copied());
|
||||
let cli = MultitoolCli::try_parse_from(cli_args).expect("parse should succeed");
|
||||
assert!(cli.interactive.strict_config);
|
||||
cli.feature_toggles
|
||||
.to_overrides()
|
||||
.expect_err("feature should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core_plugins::PluginMarketplaceUpgradeOutcome;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
@@ -123,7 +123,9 @@ async fn run_upgrade(
|
||||
args: UpgradeMarketplaceArgs,
|
||||
) -> Result<()> {
|
||||
let UpgradeMarketplaceArgs { marketplace_name } = args;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(overrides)
|
||||
.build()
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
|
||||
|
||||
+17
-31
@@ -11,6 +11,7 @@ use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_core::McpManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_global_mcp_servers;
|
||||
@@ -188,6 +189,17 @@ impl McpCli {
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_config(config_overrides: &CliConfigOverrides) -> Result<Config> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
ConfigBuilder::default()
|
||||
.cli_overrides(overrides)
|
||||
.build()
|
||||
.await
|
||||
.context("failed to load configuration")
|
||||
}
|
||||
|
||||
/// Preserve compatibility with servers that still expect the legacy empty-scope
|
||||
/// OAuth request. If a discovered-scope request is rejected by the provider,
|
||||
/// retry the login flow once without scopes.
|
||||
@@ -237,13 +249,7 @@ async fn perform_oauth_login_retry_without_scopes(
|
||||
}
|
||||
|
||||
async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Result<()> {
|
||||
// Validate any provided overrides even though they are not currently applied.
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let config = load_config(config_overrides).await?;
|
||||
|
||||
let AddArgs {
|
||||
name,
|
||||
@@ -388,12 +394,7 @@ async fn run_remove(config_overrides: &CliConfigOverrides, remove_args: RemoveAr
|
||||
}
|
||||
|
||||
async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let config = load_config(config_overrides).await?;
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
)));
|
||||
@@ -441,12 +442,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
|
||||
}
|
||||
|
||||
async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let config = load_config(config_overrides).await?;
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
)));
|
||||
@@ -473,12 +469,7 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr
|
||||
}
|
||||
|
||||
async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let config = load_config(config_overrides).await?;
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
)));
|
||||
@@ -729,12 +720,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
|
||||
}
|
||||
|
||||
async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let config = load_config(config_overrides).await?;
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
)));
|
||||
|
||||
@@ -11,6 +11,34 @@ fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_rejects_unknown_config_override() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
let mut cmd = codex_command(codex_home.path())?;
|
||||
cmd.args(["--strict-config", "-c", "foo=bar", "mcp-server"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains("unknown configuration field"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_is_not_supported_for_cloud_command() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
let mut cmd = codex_command(codex_home.path())?;
|
||||
cmd.args(["--strict-config", "-c", "foo=bar", "cloud", "list"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains(
|
||||
"`--strict-config` is not supported for `codex cloud`",
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn features_enable_writes_feature_flag_to_config() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_cloud_tasks_client::TaskStatus;
|
||||
use codex_git_utils::current_branch_name;
|
||||
use codex_git_utils::default_branch_name;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use owo_colors::OwoColorize;
|
||||
use owo_colors::Stream;
|
||||
use std::cmp::Ordering;
|
||||
@@ -40,7 +41,10 @@ struct BackendContext {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext> {
|
||||
async fn init_backend(
|
||||
user_agent_suffix: &str,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<BackendContext> {
|
||||
#[cfg(debug_assertions)]
|
||||
let use_mock = matches!(
|
||||
std::env::var("CODEX_CLOUD_TASKS_MODE").ok().as_deref(),
|
||||
@@ -68,11 +72,8 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
|
||||
};
|
||||
append_error_log(format!("startup: base_url={base_url} path_style={style}"));
|
||||
|
||||
let auth_manager = util::load_auth_manager(Some(base_url.clone())).await;
|
||||
let auth = match auth_manager.as_ref() {
|
||||
Some(manager) => manager.auth().await,
|
||||
None => None,
|
||||
};
|
||||
let auth_manager = util::load_auth_manager(Some(base_url.clone()), config_overrides).await?;
|
||||
let auth = auth_manager.auth().await;
|
||||
let auth = match auth {
|
||||
Some(auth) => auth,
|
||||
None => {
|
||||
@@ -154,14 +155,17 @@ async fn resolve_git_ref_with_git_info(
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_exec_command(args: crate::cli::ExecCommand) -> anyhow::Result<()> {
|
||||
async fn run_exec_command(
|
||||
args: crate::cli::ExecCommand,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<()> {
|
||||
let crate::cli::ExecCommand {
|
||||
query,
|
||||
environment,
|
||||
branch,
|
||||
attempts,
|
||||
} = args;
|
||||
let ctx = init_backend("codex_cloud_tasks_exec").await?;
|
||||
let ctx = init_backend("codex_cloud_tasks_exec", config_overrides).await?;
|
||||
let prompt = resolve_query_input(query)?;
|
||||
let env_id = resolve_environment_id(&ctx, &environment).await?;
|
||||
let git_ref = resolve_git_ref(branch.as_ref()).await;
|
||||
@@ -490,8 +494,11 @@ fn format_task_list_lines(
|
||||
lines
|
||||
}
|
||||
|
||||
async fn run_status_command(args: crate::cli::StatusCommand) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_status").await?;
|
||||
async fn run_status_command(
|
||||
args: crate::cli::StatusCommand,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_status", config_overrides).await?;
|
||||
let task_id = parse_task_id(&args.task_id)?;
|
||||
let summary =
|
||||
codex_cloud_tasks_client::CloudBackend::get_task_summary(&*ctx.backend, task_id).await?;
|
||||
@@ -506,8 +513,11 @@ async fn run_status_command(args: crate::cli::StatusCommand) -> anyhow::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_list_command(args: crate::cli::ListCommand) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_list").await?;
|
||||
async fn run_list_command(
|
||||
args: crate::cli::ListCommand,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_list", config_overrides).await?;
|
||||
let env_filter = if let Some(env) = args.environment {
|
||||
Some(resolve_environment_id(&ctx, &env).await?)
|
||||
} else {
|
||||
@@ -573,8 +583,11 @@ async fn run_list_command(args: crate::cli::ListCommand) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_diff_command(args: crate::cli::DiffCommand) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_diff").await?;
|
||||
async fn run_diff_command(
|
||||
args: crate::cli::DiffCommand,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_diff", config_overrides).await?;
|
||||
let task_id = parse_task_id(&args.task_id)?;
|
||||
let attempts = collect_attempt_diffs(&*ctx.backend, &task_id).await?;
|
||||
let selected = select_attempt(&attempts, args.attempt)?;
|
||||
@@ -582,8 +595,11 @@ async fn run_diff_command(args: crate::cli::DiffCommand) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_apply_command(args: crate::cli::ApplyCommand) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_apply").await?;
|
||||
async fn run_apply_command(
|
||||
args: crate::cli::ApplyCommand,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<()> {
|
||||
let ctx = init_backend("codex_cloud_tasks_apply", config_overrides).await?;
|
||||
let task_id = parse_task_id(&args.task_id)?;
|
||||
let attempts = collect_attempt_diffs(&*ctx.backend, &task_id).await?;
|
||||
let selected = select_attempt(&attempts, args.attempt)?;
|
||||
@@ -729,16 +745,19 @@ fn spawn_apply(
|
||||
|
||||
/// Entry point for the `codex cloud` subcommand.
|
||||
pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()> {
|
||||
if let Some(command) = cli.command {
|
||||
let Cli {
|
||||
config_overrides,
|
||||
command,
|
||||
} = cli;
|
||||
if let Some(command) = command {
|
||||
return match command {
|
||||
crate::cli::Command::Exec(args) => run_exec_command(args).await,
|
||||
crate::cli::Command::Status(args) => run_status_command(args).await,
|
||||
crate::cli::Command::List(args) => run_list_command(args).await,
|
||||
crate::cli::Command::Apply(args) => run_apply_command(args).await,
|
||||
crate::cli::Command::Diff(args) => run_diff_command(args).await,
|
||||
crate::cli::Command::Exec(args) => run_exec_command(args, &config_overrides).await,
|
||||
crate::cli::Command::Status(args) => run_status_command(args, &config_overrides).await,
|
||||
crate::cli::Command::List(args) => run_list_command(args, &config_overrides).await,
|
||||
crate::cli::Command::Apply(args) => run_apply_command(args, &config_overrides).await,
|
||||
crate::cli::Command::Diff(args) => run_diff_command(args, &config_overrides).await,
|
||||
};
|
||||
}
|
||||
let Cli { .. } = cli;
|
||||
|
||||
// Very minimal logging setup; mirrors other crates' pattern.
|
||||
let default_level = "error";
|
||||
@@ -753,7 +772,8 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
.try_init();
|
||||
|
||||
info!("Launching Cloud Tasks list UI");
|
||||
let BackendContext { backend, .. } = init_backend("codex_cloud_tasks_tui").await?;
|
||||
let BackendContext { backend, .. } =
|
||||
init_backend("codex_cloud_tasks_tui", &config_overrides).await?;
|
||||
let backend = backend;
|
||||
|
||||
// Terminal setup
|
||||
|
||||
@@ -3,8 +3,10 @@ use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use reqwest::header::HeaderMap;
|
||||
|
||||
use codex_core::config::Config;
|
||||
use anyhow::anyhow;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_login::AuthManager;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
|
||||
pub fn set_user_agent_suffix(suffix: &str) {
|
||||
if let Ok(mut guard) = codex_login::default_client::USER_AGENT_SUFFIX.lock() {
|
||||
@@ -41,18 +43,24 @@ pub fn normalize_base_url(input: &str) -> String {
|
||||
base_url
|
||||
}
|
||||
|
||||
pub async fn load_auth_manager(chatgpt_base_url: Option<String>) -> Option<AuthManager> {
|
||||
// TODO: pass in cli overrides once cloud tasks properly support them.
|
||||
let config = Config::load_with_cli_overrides(Vec::new()).await.ok()?;
|
||||
Some(
|
||||
AuthManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
chatgpt_base_url.or(Some(config.chatgpt_base_url)),
|
||||
)
|
||||
.await,
|
||||
pub async fn load_auth_manager(
|
||||
chatgpt_base_url: Option<String>,
|
||||
config_overrides: &CliConfigOverrides,
|
||||
) -> anyhow::Result<AuthManager> {
|
||||
let cli_overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(|err| anyhow!("Error parsing -c overrides: {err}"))?;
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_overrides)
|
||||
.build()
|
||||
.await?;
|
||||
Ok(AuthManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
chatgpt_base_url.or(Some(config.chatgpt_base_url)),
|
||||
)
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Build headers for ChatGPT-backed requests: `User-Agent`, optional `Authorization`,
|
||||
@@ -68,7 +76,8 @@ pub async fn build_chatgpt_headers() -> HeaderMap {
|
||||
USER_AGENT,
|
||||
HeaderValue::from_str(&ua).unwrap_or(HeaderValue::from_static("codex-cli")),
|
||||
);
|
||||
if let Some(am) = load_auth_manager(/*chatgpt_base_url*/ None).await
|
||||
let config_overrides = CliConfigOverrides::default();
|
||||
if let Ok(am) = load_auth_manager(/*chatgpt_base_url*/ None, &config_overrides).await
|
||||
&& let Some(auth) = am.auth().await
|
||||
&& auth.uses_codex_backend()
|
||||
{
|
||||
|
||||
@@ -32,6 +32,7 @@ multimap = { workspace = true }
|
||||
prost = "0.14.3"
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_ignored = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_path_to_error = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
@@ -86,6 +86,23 @@ impl std::error::Error for ConfigLoadError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum ConfigDiagnosticSource<'a> {
|
||||
Path(&'a Path),
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
DisplayName(&'a str),
|
||||
}
|
||||
|
||||
impl ConfigDiagnosticSource<'_> {
|
||||
pub(crate) fn to_path_buf(self) -> PathBuf {
|
||||
match self {
|
||||
ConfigDiagnosticSource::Path(path) => path.to_path_buf(),
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
ConfigDiagnosticSource::DisplayName(name) => PathBuf::from(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn io_error_from_config_error(
|
||||
kind: io::ErrorKind,
|
||||
error: ConfigError,
|
||||
@@ -98,21 +115,39 @@ pub fn config_error_from_toml(
|
||||
path: impl AsRef<Path>,
|
||||
contents: &str,
|
||||
err: toml::de::Error,
|
||||
) -> ConfigError {
|
||||
config_error_from_toml_for_source(ConfigDiagnosticSource::Path(path.as_ref()), contents, err)
|
||||
}
|
||||
|
||||
pub(crate) fn config_error_from_toml_for_source(
|
||||
source: ConfigDiagnosticSource<'_>,
|
||||
contents: &str,
|
||||
err: toml::de::Error,
|
||||
) -> ConfigError {
|
||||
let range = err
|
||||
.span()
|
||||
.map(|span| text_range_from_span(contents, span))
|
||||
.unwrap_or_else(default_range);
|
||||
ConfigError::new(path.as_ref().to_path_buf(), range, err.message())
|
||||
ConfigError::new(source.to_path_buf(), range, err.message())
|
||||
}
|
||||
|
||||
pub fn config_error_from_typed_toml<T: DeserializeOwned>(
|
||||
path: impl AsRef<Path>,
|
||||
contents: &str,
|
||||
) -> Option<ConfigError> {
|
||||
config_error_from_typed_toml_for_source::<T>(
|
||||
ConfigDiagnosticSource::Path(path.as_ref()),
|
||||
contents,
|
||||
)
|
||||
}
|
||||
|
||||
fn config_error_from_typed_toml_for_source<T: DeserializeOwned>(
|
||||
source: ConfigDiagnosticSource<'_>,
|
||||
contents: &str,
|
||||
) -> Option<ConfigError> {
|
||||
let deserializer = match toml::de::Deserializer::parse(contents) {
|
||||
Ok(deserializer) => deserializer,
|
||||
Err(err) => return Some(config_error_from_toml(path, contents, err)),
|
||||
Err(err) => return Some(config_error_from_toml_for_source(source, contents, err)),
|
||||
};
|
||||
|
||||
let result: Result<T, _> = serde_path_to_error::deserialize(deserializer);
|
||||
@@ -126,7 +161,7 @@ pub fn config_error_from_typed_toml<T: DeserializeOwned>(
|
||||
.map(|span| text_range_from_span(contents, span))
|
||||
.unwrap_or_else(default_range);
|
||||
Some(ConfigError::new(
|
||||
path.as_ref().to_path_buf(),
|
||||
source.to_path_buf(),
|
||||
range,
|
||||
toml_err.message(),
|
||||
))
|
||||
@@ -205,7 +240,7 @@ fn config_path_for_layer(layer: &ConfigLayerEntry, config_toml_file: &str) -> Op
|
||||
}
|
||||
}
|
||||
|
||||
fn text_range_from_span(contents: &str, span: std::ops::Range<usize>) -> TextRange {
|
||||
pub(crate) fn text_range_from_span(contents: &str, span: std::ops::Range<usize>) -> TextRange {
|
||||
let start = position_for_offset(contents, span.start);
|
||||
let end_index = if span.end > span.start {
|
||||
span.end - 1
|
||||
@@ -290,7 +325,7 @@ fn position_for_offset(contents: &str, index: usize) -> TextPosition {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_range() -> TextRange {
|
||||
pub(crate) fn default_range() -> TextRange {
|
||||
let position = TextPosition { line: 1, column: 1 };
|
||||
TextRange {
|
||||
start: position,
|
||||
@@ -314,7 +349,10 @@ fn span_for_path(contents: &str, path: &SerdePath) -> Option<std::ops::Range<usi
|
||||
}
|
||||
}
|
||||
|
||||
fn span_for_config_path(contents: &str, path: &SerdePath) -> Option<std::ops::Range<usize>> {
|
||||
pub(crate) fn span_for_config_path(
|
||||
contents: &str,
|
||||
path: &SerdePath,
|
||||
) -> Option<std::ops::Range<usize>> {
|
||||
if is_features_table_path(path)
|
||||
&& let Some(span) = span_for_features_value(contents)
|
||||
{
|
||||
@@ -323,6 +361,48 @@ fn span_for_config_path(contents: &str, path: &SerdePath) -> Option<std::ops::Ra
|
||||
span_for_path(contents, path)
|
||||
}
|
||||
|
||||
pub(crate) fn span_for_toml_key_path(
|
||||
contents: &str,
|
||||
path: &[String],
|
||||
) -> Option<std::ops::Range<usize>> {
|
||||
let doc = contents.parse::<Document<String>>().ok()?;
|
||||
let mut node = TomlNode::Item(doc.as_item());
|
||||
for (index, segment) in path.iter().enumerate() {
|
||||
if index + 1 == path.len() {
|
||||
let key_span = match &node {
|
||||
TomlNode::Item(item) => item
|
||||
.as_table_like()
|
||||
.and_then(|table| table.get_key_value(segment))
|
||||
.and_then(|(key, _)| key.span()),
|
||||
TomlNode::Table(table) => {
|
||||
table.get_key_value(segment).and_then(|(key, _)| key.span())
|
||||
}
|
||||
TomlNode::Value(Value::InlineTable(table)) => {
|
||||
table.get_key_value(segment).and_then(|(key, _)| key.span())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if key_span.is_some() {
|
||||
return key_span;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(next) = map_child(&node, segment) {
|
||||
node = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = segment.parse::<usize>().ok()?;
|
||||
node = seq_child(&node, index)?;
|
||||
}
|
||||
|
||||
match node {
|
||||
TomlNode::Item(item) => item.span(),
|
||||
TomlNode::Table(table) => table.span(),
|
||||
TomlNode::Value(value) => value.span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_features_table_path(path: &SerdePath) -> bool {
|
||||
let mut segments = path.iter();
|
||||
matches!(segments.next(), Some(SerdeSegment::Map { key }) if key == "features")
|
||||
|
||||
@@ -21,6 +21,7 @@ mod requirements_exec_policy;
|
||||
pub mod schema;
|
||||
mod skills_config;
|
||||
mod state;
|
||||
mod strict_config;
|
||||
mod thread_config;
|
||||
mod tui_keymap;
|
||||
pub mod types;
|
||||
@@ -116,7 +117,9 @@ pub use skills_config::SkillsConfig;
|
||||
pub use state::ConfigLayerEntry;
|
||||
pub use state::ConfigLayerStack;
|
||||
pub use state::ConfigLayerStackOrdering;
|
||||
pub use state::ConfigLoadOptions;
|
||||
pub use state::LoaderOverrides;
|
||||
pub use strict_config::config_error_from_ignored_toml_fields;
|
||||
pub use thread_config::NoopThreadConfigLoader;
|
||||
pub use thread_config::RemoteThreadConfigLoader;
|
||||
pub use thread_config::SessionThreadConfig;
|
||||
|
||||
@@ -10,13 +10,14 @@ This module is the canonical place to **load and describe Codex configuration la
|
||||
|
||||
Exported from `codex_config::loader`:
|
||||
|
||||
- `load_config_layers_state(fs, codex_home, cwd_opt, cli_overrides, overrides, cloud_requirements, thread_config_loader) -> ConfigLayerStack`
|
||||
- `load_config_layers_state(fs, codex_home, cwd_opt, cli_overrides, options, cloud_requirements, thread_config_loader) -> ConfigLayerStack`
|
||||
- `ConfigLayerStack`
|
||||
- `effective_config() -> toml::Value`
|
||||
- `origins() -> HashMap<String, ConfigLayerMetadata>`
|
||||
- `layers_high_to_low() -> Vec<ConfigLayer>`
|
||||
- `with_user_config(user_config) -> ConfigLayerStack`
|
||||
- `ConfigLayerEntry` (one layer’s `{name, config, version, disabled_reason}`; `name` carries source metadata)
|
||||
- `ConfigLoadOptions` (user-facing load behavior such as strict config validation)
|
||||
- `LoaderOverrides` (test/override hooks for managed config sources)
|
||||
- `merge_toml_values(base, overlay)` (public helper used elsewhere)
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
use super::macos::ManagedAdminConfigLayer;
|
||||
#[cfg(target_os = "macos")]
|
||||
use super::macos::load_managed_admin_config_layer;
|
||||
use crate::config_toml::ConfigToml;
|
||||
use crate::diagnostics::config_error_from_toml;
|
||||
use crate::diagnostics::io_error_from_config_error;
|
||||
use crate::state::LoaderOverrides;
|
||||
use crate::strict_config::config_error_from_ignored_toml_value_fields;
|
||||
use codex_file_system::ExecutorFileSystem;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
@@ -39,6 +42,7 @@ pub(super) async fn load_config_layers_internal(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
codex_home: &Path,
|
||||
overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
) -> io::Result<LoadedConfigLayers> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let LoaderOverrides {
|
||||
@@ -57,19 +61,26 @@ pub(super) async fn load_config_layers_internal(
|
||||
managed_config_path.unwrap_or_else(|| managed_config_default_path(codex_home)),
|
||||
)?;
|
||||
|
||||
let managed_config =
|
||||
read_config_from_path(fs, &managed_config_path, /*log_missing_as_info*/ false)
|
||||
.await?
|
||||
.map(|managed_config| MangedConfigFromFile {
|
||||
managed_config,
|
||||
file: managed_config_path.clone(),
|
||||
});
|
||||
let managed_config = read_config_from_path(
|
||||
fs,
|
||||
&managed_config_path,
|
||||
/*log_missing_as_info*/ false,
|
||||
strict_config,
|
||||
)
|
||||
.await?
|
||||
.map(|loaded| MangedConfigFromFile {
|
||||
managed_config: loaded,
|
||||
file: managed_config_path.clone(),
|
||||
});
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let managed_preferences =
|
||||
load_managed_admin_config_layer(managed_preferences_base64.as_deref())
|
||||
.await?
|
||||
.map(map_managed_admin_layer);
|
||||
let managed_preferences = load_managed_admin_config_layer(
|
||||
managed_preferences_base64.as_deref(),
|
||||
strict_config,
|
||||
codex_home,
|
||||
)
|
||||
.await?
|
||||
.map(map_managed_admin_layer);
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let managed_preferences = None;
|
||||
@@ -93,10 +104,14 @@ pub(super) async fn read_config_from_path(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
path: &AbsolutePathBuf,
|
||||
log_missing_as_info: bool,
|
||||
strict_config: bool,
|
||||
) -> io::Result<Option<TomlValue>> {
|
||||
match fs.read_file_text(path, /*sandbox*/ None).await {
|
||||
Ok(contents) => match toml::from_str::<TomlValue>(&contents) {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
Ok(value) => {
|
||||
validate_config_toml_strictly_if_requested(strict_config, path, &contents, &value)?;
|
||||
Ok(Some(value))
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to parse {}: {err}", path.as_path().display());
|
||||
let config_error = config_error_from_toml(path.as_path(), &contents, err.clone());
|
||||
@@ -122,6 +137,38 @@ pub(super) async fn read_config_from_path(
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_config_toml_strictly_if_requested(
|
||||
strict_config: bool,
|
||||
path: &AbsolutePathBuf,
|
||||
contents: &str,
|
||||
value: &TomlValue,
|
||||
) -> io::Result<()> {
|
||||
if !strict_config {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(base_dir) = path.as_path().parent() else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Config file {} has no parent directory", path.display()),
|
||||
));
|
||||
};
|
||||
let _guard = AbsolutePathBufGuard::new(base_dir);
|
||||
if let Some(config_error) = config_error_from_ignored_toml_value_fields::<ConfigToml>(
|
||||
path.as_path(),
|
||||
contents,
|
||||
value.clone(),
|
||||
) {
|
||||
return Err(io_error_from_config_error(
|
||||
io::ErrorKind::InvalidData,
|
||||
config_error,
|
||||
/*source*/ None,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the default managed config path.
|
||||
pub(super) fn managed_config_default_path(codex_home: &Path) -> PathBuf {
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -2,13 +2,20 @@ use super::merge_requirements_with_remote_sandbox_config;
|
||||
use crate::config_requirements::ConfigRequirementsToml;
|
||||
use crate::config_requirements::ConfigRequirementsWithSources;
|
||||
use crate::config_requirements::RequirementSource;
|
||||
use crate::config_toml::ConfigToml;
|
||||
use crate::diagnostics::ConfigDiagnosticSource;
|
||||
use crate::diagnostics::config_error_from_toml_for_source;
|
||||
use crate::diagnostics::io_error_from_config_error;
|
||||
use crate::strict_config::config_error_from_ignored_toml_value_fields_for_source_name;
|
||||
use base64::Engine;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation::string::CFStringRef;
|
||||
use std::ffi::c_void;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use tokio::task;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
@@ -31,17 +38,20 @@ pub(super) fn managed_preferences_requirements_source() -> RequirementSource {
|
||||
|
||||
pub(crate) async fn load_managed_admin_config_layer(
|
||||
override_base64: Option<&str>,
|
||||
strict_config: bool,
|
||||
base_dir: &Path,
|
||||
) -> io::Result<Option<ManagedAdminConfigLayer>> {
|
||||
if let Some(encoded) = override_base64 {
|
||||
let trimmed = encoded.trim();
|
||||
return if trimmed.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
parse_managed_config_base64(trimmed).map(Some)
|
||||
parse_managed_config_base64(trimmed, strict_config, base_dir).map(Some)
|
||||
};
|
||||
}
|
||||
|
||||
match task::spawn_blocking(load_managed_admin_config).await {
|
||||
let base_dir = base_dir.to_path_buf();
|
||||
match task::spawn_blocking(move || load_managed_admin_config(strict_config, &base_dir)).await {
|
||||
Ok(result) => result,
|
||||
Err(join_err) => {
|
||||
if join_err.is_cancelled() {
|
||||
@@ -54,11 +64,14 @@ pub(crate) async fn load_managed_admin_config_layer(
|
||||
}
|
||||
}
|
||||
|
||||
fn load_managed_admin_config() -> io::Result<Option<ManagedAdminConfigLayer>> {
|
||||
fn load_managed_admin_config(
|
||||
strict_config: bool,
|
||||
base_dir: &Path,
|
||||
) -> io::Result<Option<ManagedAdminConfigLayer>> {
|
||||
load_managed_preference(MANAGED_PREFERENCES_CONFIG_KEY)?
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.map(parse_managed_config_base64)
|
||||
.map(|encoded| parse_managed_config_base64(encoded, strict_config, base_dir))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
@@ -134,24 +147,73 @@ fn load_managed_preference(key_name: &str) -> io::Result<Option<String>> {
|
||||
Ok(Some(value))
|
||||
}
|
||||
|
||||
fn parse_managed_config_base64(encoded: &str) -> io::Result<ManagedAdminConfigLayer> {
|
||||
fn parse_managed_config_base64(
|
||||
encoded: &str,
|
||||
strict_config: bool,
|
||||
base_dir: &Path,
|
||||
) -> io::Result<ManagedAdminConfigLayer> {
|
||||
let raw_toml = decode_managed_preferences_base64(encoded)?;
|
||||
match toml::from_str::<TomlValue>(&raw_toml) {
|
||||
Ok(TomlValue::Table(parsed)) => Ok(ManagedAdminConfigLayer {
|
||||
let source_name =
|
||||
format!("{MANAGED_PREFERENCES_APPLICATION_ID}:{MANAGED_PREFERENCES_CONFIG_KEY}");
|
||||
let parsed = toml::from_str::<TomlValue>(&raw_toml).map_err(|err| {
|
||||
tracing::error!("Failed to parse managed config TOML: {err}");
|
||||
if strict_config {
|
||||
let config_error = config_error_from_toml_for_source(
|
||||
ConfigDiagnosticSource::DisplayName(&source_name),
|
||||
&raw_toml,
|
||||
err.clone(),
|
||||
);
|
||||
io_error_from_config_error(io::ErrorKind::InvalidData, config_error, Some(err))
|
||||
} else {
|
||||
io::Error::new(io::ErrorKind::InvalidData, err)
|
||||
}
|
||||
})?;
|
||||
|
||||
validate_managed_config_toml_strictly_if_requested(
|
||||
strict_config,
|
||||
&source_name,
|
||||
&raw_toml,
|
||||
&parsed,
|
||||
base_dir,
|
||||
)?;
|
||||
match parsed {
|
||||
TomlValue::Table(parsed) => Ok(ManagedAdminConfigLayer {
|
||||
config: TomlValue::Table(parsed),
|
||||
raw_toml,
|
||||
}),
|
||||
Ok(other) => {
|
||||
other => {
|
||||
tracing::error!("Managed config TOML must have a table at the root, found {other:?}",);
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"managed config root must be a table",
|
||||
))
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to parse managed config TOML: {err}");
|
||||
Err(io::Error::new(io::ErrorKind::InvalidData, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_managed_config_toml_strictly_if_requested(
|
||||
strict_config: bool,
|
||||
source_name: &str,
|
||||
raw_toml: &str,
|
||||
parsed: &TomlValue,
|
||||
base_dir: &Path,
|
||||
) -> io::Result<()> {
|
||||
if !strict_config {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _guard = AbsolutePathBufGuard::new(base_dir);
|
||||
if let Some(config_error) = config_error_from_ignored_toml_value_fields_for_source_name::<
|
||||
ConfigToml,
|
||||
>(source_name, raw_toml, parsed.clone())
|
||||
{
|
||||
Err(io_error_from_config_error(
|
||||
io::ErrorKind::InvalidData,
|
||||
config_error,
|
||||
/*source*/ None,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,11 @@ use crate::project_root_markers::default_project_root_markers;
|
||||
use crate::project_root_markers::project_root_markers_from_config;
|
||||
use crate::state::ConfigLayerEntry;
|
||||
use crate::state::ConfigLayerStack;
|
||||
use crate::state::ConfigLoadOptions;
|
||||
use crate::state::LoaderOverrides;
|
||||
use crate::strict_config::config_error_from_ignored_toml_value_fields;
|
||||
use crate::strict_config::ignored_toml_value_field;
|
||||
use crate::strict_config::unknown_feature_toml_value_field;
|
||||
use crate::thread_config::ThreadConfigContext;
|
||||
use crate::thread_config::ThreadConfigLoader;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
@@ -104,10 +108,14 @@ pub async fn load_config_layers_state(
|
||||
codex_home: &Path,
|
||||
cwd: Option<AbsolutePathBuf>,
|
||||
cli_overrides: &[(String, TomlValue)],
|
||||
overrides: LoaderOverrides,
|
||||
options: impl Into<ConfigLoadOptions>,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
thread_config_loader: &dyn ThreadConfigLoader,
|
||||
) -> io::Result<ConfigLayerStack> {
|
||||
let ConfigLoadOptions {
|
||||
loader_overrides: overrides,
|
||||
strict_config,
|
||||
} = options.into();
|
||||
let ignore_managed_requirements = overrides.ignore_managed_requirements;
|
||||
let ignore_user_config = overrides.ignore_user_config;
|
||||
let ignore_user_and_project_exec_policy_rules =
|
||||
@@ -140,7 +148,8 @@ pub async fn load_config_layers_state(
|
||||
// Make a best-effort to support the legacy `managed_config.toml` as a
|
||||
// requirements specification.
|
||||
let loaded_config_layers =
|
||||
layer_io::load_config_layers_internal(fs, codex_home, overrides.clone()).await?;
|
||||
layer_io::load_config_layers_internal(fs, codex_home, overrides.clone(), strict_config)
|
||||
.await?;
|
||||
if !ignore_managed_requirements {
|
||||
load_requirements_from_legacy_scheme(
|
||||
&mut config_requirements_toml,
|
||||
@@ -168,6 +177,11 @@ pub async fn load_config_layers_state(
|
||||
.as_ref()
|
||||
.map(AbsolutePathBuf::as_path)
|
||||
.unwrap_or(codex_home);
|
||||
validate_cli_overrides_strictly_if_requested(
|
||||
strict_config,
|
||||
&cli_overrides_layer,
|
||||
base_dir,
|
||||
)?;
|
||||
Some(resolve_relative_paths_in_config_toml(
|
||||
cli_overrides_layer,
|
||||
base_dir,
|
||||
@@ -177,16 +191,20 @@ pub async fn load_config_layers_state(
|
||||
// Include an entry for the "system" config folder, loading its config.toml,
|
||||
// if it exists.
|
||||
let system_config_toml_file = system_config_toml_file_with_overrides(&overrides)?;
|
||||
let system_layer =
|
||||
load_config_toml_for_required_layer(fs, &system_config_toml_file, |config_toml| {
|
||||
let system_layer = load_config_toml_for_required_layer(
|
||||
fs,
|
||||
&system_config_toml_file,
|
||||
strict_config,
|
||||
|config_toml| {
|
||||
ConfigLayerEntry::new(
|
||||
ConfigLayerSource::System {
|
||||
file: system_config_toml_file.clone(),
|
||||
},
|
||||
config_toml,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
layers.push(system_layer);
|
||||
|
||||
// Add a layer for $CODEX_HOME/config.toml so folder-derived resources such
|
||||
@@ -201,7 +219,7 @@ pub async fn load_config_layers_state(
|
||||
TomlValue::Table(toml::map::Map::new()),
|
||||
)
|
||||
} else {
|
||||
load_config_toml_for_required_layer(fs, &user_file, |config_toml| {
|
||||
load_config_toml_for_required_layer(fs, &user_file, strict_config, |config_toml| {
|
||||
ConfigLayerEntry::new(
|
||||
ConfigLayerSource::User {
|
||||
file: user_file.clone(),
|
||||
@@ -268,6 +286,7 @@ pub async fn load_config_layers_state(
|
||||
&project_trust_context.project_root,
|
||||
&project_trust_context,
|
||||
codex_home,
|
||||
strict_config,
|
||||
)
|
||||
.await?;
|
||||
layers.extend(project_layers.layers);
|
||||
@@ -359,15 +378,11 @@ fn insert_layer_by_precedence(layers: &mut Vec<ConfigLayerEntry>, layer: ConfigL
|
||||
async fn load_config_toml_for_required_layer(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
toml_file: &AbsolutePathBuf,
|
||||
strict_config: bool,
|
||||
create_entry: impl FnOnce(TomlValue) -> ConfigLayerEntry,
|
||||
) -> io::Result<ConfigLayerEntry> {
|
||||
let toml_value = match fs.read_file_text(toml_file, /*sandbox*/ None).await {
|
||||
Ok(contents) => {
|
||||
let config: TomlValue = toml::from_str(&contents).map_err(|err| {
|
||||
let config_error =
|
||||
config_error_from_toml(toml_file.as_path(), &contents, err.clone());
|
||||
io_error_from_config_error(io::ErrorKind::InvalidData, config_error, Some(err))
|
||||
})?;
|
||||
let config_parent = toml_file.as_path().parent().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
@@ -377,6 +392,18 @@ async fn load_config_toml_for_required_layer(
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let config: TomlValue = toml::from_str(&contents).map_err(|err| {
|
||||
let config_error =
|
||||
config_error_from_toml(toml_file.as_path(), &contents, err.clone());
|
||||
io_error_from_config_error(io::ErrorKind::InvalidData, config_error, Some(err))
|
||||
})?;
|
||||
validate_config_toml_strictly_if_requested(
|
||||
strict_config,
|
||||
toml_file.as_path(),
|
||||
&contents,
|
||||
&config,
|
||||
config_parent,
|
||||
)?;
|
||||
resolve_relative_paths_in_config_toml(config, config_parent)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -397,6 +424,61 @@ async fn load_config_toml_for_required_layer(
|
||||
Ok(create_entry(toml_value))
|
||||
}
|
||||
|
||||
fn validate_config_toml_strictly_if_requested(
|
||||
strict_config: bool,
|
||||
toml_file: &Path,
|
||||
contents: &str,
|
||||
value: &TomlValue,
|
||||
base_dir: &Path,
|
||||
) -> io::Result<()> {
|
||||
if !strict_config {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _guard = AbsolutePathBufGuard::new(base_dir);
|
||||
if let Some(config_error) = config_error_from_ignored_toml_value_fields::<ConfigToml>(
|
||||
toml_file,
|
||||
contents,
|
||||
value.clone(),
|
||||
) {
|
||||
Err(io_error_from_config_error(
|
||||
io::ErrorKind::InvalidData,
|
||||
config_error,
|
||||
/*source*/ None,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_cli_overrides_strictly_if_requested(
|
||||
strict_config: bool,
|
||||
cli_overrides_layer: &TomlValue,
|
||||
base_dir: &Path,
|
||||
) -> io::Result<()> {
|
||||
if !strict_config {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _guard = AbsolutePathBufGuard::new(base_dir);
|
||||
if let Some(ignored_path) = ignored_toml_value_field::<ConfigToml>(cli_overrides_layer.clone())
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("unknown configuration field `{ignored_path}` in -c/--config override"),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ignored_path) = unknown_feature_toml_value_field(cli_overrides_layer) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("unknown configuration field `{ignored_path}` in -c/--config override"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If available, apply requirements from the platform system
|
||||
/// `requirements.toml` location to `config_requirements_toml` by filling in
|
||||
/// any unset fields.
|
||||
@@ -961,6 +1043,7 @@ async fn load_project_layers(
|
||||
project_root: &AbsolutePathBuf,
|
||||
trust_context: &ProjectTrustContext,
|
||||
codex_home: &Path,
|
||||
strict_config: bool,
|
||||
) -> io::Result<LoadedProjectLayers> {
|
||||
let codex_home_abs = AbsolutePathBuf::from_absolute_path(codex_home)?;
|
||||
let codex_home_normalized =
|
||||
@@ -1024,6 +1107,15 @@ async fn load_project_layers(
|
||||
}
|
||||
};
|
||||
let mut config = config;
|
||||
if disabled_reason.is_none() {
|
||||
validate_config_toml_strictly_if_requested(
|
||||
strict_config,
|
||||
config_file.as_path(),
|
||||
&contents,
|
||||
&config,
|
||||
dot_codex_abs.as_path(),
|
||||
)?;
|
||||
}
|
||||
let ignored_project_config_keys = sanitize_project_config(&mut config);
|
||||
let config =
|
||||
resolve_relative_paths_in_config_toml(config, dot_codex_abs.as_path())?;
|
||||
|
||||
@@ -14,6 +14,22 @@ use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
/// User-facing config loading behavior that is not part of the config document.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConfigLoadOptions {
|
||||
pub loader_overrides: LoaderOverrides,
|
||||
pub strict_config: bool,
|
||||
}
|
||||
|
||||
impl From<LoaderOverrides> for ConfigLoadOptions {
|
||||
fn from(loader_overrides: LoaderOverrides) -> Self {
|
||||
Self {
|
||||
loader_overrides,
|
||||
strict_config: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// LoaderOverrides overrides managed configuration inputs (primarily for tests).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LoaderOverrides {
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
//! Strict config validation built on top of serde's ignored-field tracking.
|
||||
|
||||
use crate::diagnostics::ConfigDiagnosticSource;
|
||||
use crate::diagnostics::ConfigError;
|
||||
use crate::diagnostics::config_error_from_toml_for_source;
|
||||
use crate::diagnostics::default_range;
|
||||
use crate::diagnostics::span_for_config_path;
|
||||
use crate::diagnostics::span_for_toml_key_path;
|
||||
use crate::diagnostics::text_range_from_span;
|
||||
use codex_features::is_known_feature_key;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::path::Path;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
pub fn config_error_from_ignored_toml_fields<T: DeserializeOwned>(
|
||||
path: impl AsRef<Path>,
|
||||
contents: &str,
|
||||
) -> Option<ConfigError> {
|
||||
let source = ConfigDiagnosticSource::Path(path.as_ref());
|
||||
match toml::from_str::<TomlValue>(contents) {
|
||||
Ok(value) => {
|
||||
config_error_from_ignored_toml_value_fields_for_source::<T>(source, contents, value)
|
||||
}
|
||||
Err(err) => Some(config_error_from_toml_for_source(source, contents, err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn config_error_from_ignored_toml_value_fields<T: DeserializeOwned>(
|
||||
path: impl AsRef<Path>,
|
||||
contents: &str,
|
||||
value: TomlValue,
|
||||
) -> Option<ConfigError> {
|
||||
config_error_from_ignored_toml_value_fields_for_source::<T>(
|
||||
ConfigDiagnosticSource::Path(path.as_ref()),
|
||||
contents,
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
pub(crate) fn config_error_from_ignored_toml_value_fields_for_source_name<T: DeserializeOwned>(
|
||||
source_name: &str,
|
||||
contents: &str,
|
||||
value: TomlValue,
|
||||
) -> Option<ConfigError> {
|
||||
config_error_from_ignored_toml_value_fields_for_source::<T>(
|
||||
ConfigDiagnosticSource::DisplayName(source_name),
|
||||
contents,
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
fn config_error_from_ignored_toml_value_fields_for_source<T: DeserializeOwned>(
|
||||
source: ConfigDiagnosticSource<'_>,
|
||||
contents: &str,
|
||||
value: TomlValue,
|
||||
) -> Option<ConfigError> {
|
||||
let unknown_feature_paths = unknown_feature_toml_value_path(&value);
|
||||
let mut ignored_paths = Vec::new();
|
||||
let mut ignored_callback = |ignored_path: serde_ignored::Path<'_>| {
|
||||
let path_segments = ignored_path_segments(&ignored_path);
|
||||
if !path_segments.is_empty() {
|
||||
ignored_paths.push(path_segments);
|
||||
}
|
||||
};
|
||||
let deserializer = serde_ignored::Deserializer::new(value, &mut ignored_callback);
|
||||
let result: Result<T, _> = serde_path_to_error::deserialize(deserializer);
|
||||
|
||||
match result {
|
||||
Ok(_) => unknown_field_error_from_paths(source, contents, ignored_paths)
|
||||
.or_else(|| unknown_field_error_from_paths(source, contents, unknown_feature_paths)),
|
||||
Err(err) => {
|
||||
let path_hint = err.path().clone();
|
||||
let toml_err = err.into_inner();
|
||||
let range = span_for_config_path(contents, &path_hint)
|
||||
.or_else(|| toml_err.span())
|
||||
.map(|span| text_range_from_span(contents, span))
|
||||
.unwrap_or_else(default_range);
|
||||
Some(ConfigError::new(
|
||||
source.to_path_buf(),
|
||||
range,
|
||||
toml_err.message(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ignored_toml_value_field<T: DeserializeOwned>(value: TomlValue) -> Option<String> {
|
||||
let mut ignored_paths = Vec::new();
|
||||
let result: Result<T, _> = serde_ignored::deserialize(value, |ignored_path| {
|
||||
let path_segments = ignored_path_segments(&ignored_path);
|
||||
if !path_segments.is_empty() {
|
||||
ignored_paths.push(path_segments);
|
||||
}
|
||||
});
|
||||
if result.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
ignored_paths
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|path_segments| path_segments.join("."))
|
||||
}
|
||||
|
||||
pub(crate) fn unknown_feature_toml_value_field(value: &TomlValue) -> Option<String> {
|
||||
unknown_feature_toml_value_path(value)
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|path_segments| path_segments.join("."))
|
||||
}
|
||||
|
||||
fn unknown_field_error_from_paths(
|
||||
source: ConfigDiagnosticSource<'_>,
|
||||
contents: &str,
|
||||
ignored_paths: Vec<Vec<String>>,
|
||||
) -> Option<ConfigError> {
|
||||
let path_segments = ignored_paths.into_iter().next()?;
|
||||
let ignored_path = path_segments.join(".");
|
||||
let range = span_for_toml_key_path(contents, &path_segments)
|
||||
.map(|span| text_range_from_span(contents, span))
|
||||
.unwrap_or_else(default_range);
|
||||
Some(ConfigError::new(
|
||||
source.to_path_buf(),
|
||||
range,
|
||||
format!("unknown configuration field `{ignored_path}`"),
|
||||
))
|
||||
}
|
||||
|
||||
fn unknown_feature_toml_value_path(value: &TomlValue) -> Vec<Vec<String>> {
|
||||
let Some(root) = value.as_table() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut paths = Vec::new();
|
||||
push_unknown_feature_paths(&mut paths, &["features"], root.get("features"));
|
||||
|
||||
if let Some(profiles) = root.get("profiles").and_then(TomlValue::as_table) {
|
||||
for (profile_name, profile) in profiles {
|
||||
let prefix = ["profiles", profile_name.as_str(), "features"];
|
||||
let features = profile
|
||||
.as_table()
|
||||
.and_then(|profile| profile.get("features"));
|
||||
push_unknown_feature_paths(&mut paths, &prefix, features);
|
||||
}
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
fn push_unknown_feature_paths(
|
||||
paths: &mut Vec<Vec<String>>,
|
||||
prefix: &[&str],
|
||||
features: Option<&TomlValue>,
|
||||
) {
|
||||
let Some(features) = features.and_then(TomlValue::as_table) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for feature_key in features
|
||||
.keys()
|
||||
.map(String::as_str)
|
||||
.filter(|key| !is_known_feature_key(key))
|
||||
{
|
||||
let mut path = prefix
|
||||
.iter()
|
||||
.map(|segment| (*segment).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
path.push(feature_key.to_string());
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
fn ignored_path_segments(path: &serde_ignored::Path<'_>) -> Vec<String> {
|
||||
let mut segments = Vec::new();
|
||||
push_ignored_path_segments(path, &mut segments);
|
||||
segments
|
||||
}
|
||||
|
||||
fn push_ignored_path_segments(path: &serde_ignored::Path<'_>, segments: &mut Vec<String>) {
|
||||
match path {
|
||||
serde_ignored::Path::Root => {}
|
||||
serde_ignored::Path::Seq { parent, index } => {
|
||||
push_ignored_path_segments(parent, segments);
|
||||
segments.push(index.to_string());
|
||||
}
|
||||
serde_ignored::Path::Map { parent, key } => {
|
||||
push_ignored_path_segments(parent, segments);
|
||||
segments.push(key.clone());
|
||||
}
|
||||
serde_ignored::Path::Some { parent }
|
||||
| serde_ignored::Path::NewtypeStruct { parent }
|
||||
| serde_ignored::Path::NewtypeVariant { parent } => {
|
||||
push_ignored_path_segments(parent, segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "strict_config_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,112 @@
|
||||
use super::*;
|
||||
use crate::config_toml::ConfigToml;
|
||||
use crate::diagnostics::TextPosition;
|
||||
use crate::diagnostics::TextRange;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn ignored_toml_field_errors_accept_non_file_source_names() {
|
||||
let source_name = "com.openai.codex:config_toml_base64";
|
||||
let contents = r#"
|
||||
model = "gpt-5"
|
||||
unknown_key = true"#;
|
||||
|
||||
let value = toml::from_str::<TomlValue>(contents).expect("valid TOML");
|
||||
let error = config_error_from_ignored_toml_value_fields_for_source_name::<ConfigToml>(
|
||||
source_name,
|
||||
contents,
|
||||
value,
|
||||
)
|
||||
.expect("unknown field error");
|
||||
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigError::new(
|
||||
PathBuf::from(source_name),
|
||||
TextRange {
|
||||
start: TextPosition { line: 3, column: 1 },
|
||||
end: TextPosition {
|
||||
line: 3,
|
||||
column: 11,
|
||||
},
|
||||
},
|
||||
"unknown configuration field `unknown_key`",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_errors_take_precedence_over_ignored_fields() {
|
||||
let path = Path::new("/tmp/config.toml");
|
||||
let contents = r#"
|
||||
model_context_window = "wide"
|
||||
unknown_key = true"#;
|
||||
|
||||
let error =
|
||||
config_error_from_ignored_toml_fields::<ConfigToml>(path, contents).expect("type error");
|
||||
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigError::new(
|
||||
path.to_path_buf(),
|
||||
TextRange {
|
||||
start: TextPosition {
|
||||
line: 2,
|
||||
column: 24,
|
||||
},
|
||||
end: TextPosition {
|
||||
line: 2,
|
||||
column: 29,
|
||||
},
|
||||
},
|
||||
"invalid type: string \"wide\", expected i64",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_rejects_unknown_feature_key() {
|
||||
let path = Path::new("/tmp/config.toml");
|
||||
let contents = r#"
|
||||
[features]
|
||||
foo = true"#;
|
||||
|
||||
let error = config_error_from_ignored_toml_fields::<ConfigToml>(path, contents)
|
||||
.expect("unknown feature error");
|
||||
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigError::new(
|
||||
path.to_path_buf(),
|
||||
TextRange {
|
||||
start: TextPosition { line: 3, column: 1 },
|
||||
end: TextPosition { line: 3, column: 3 },
|
||||
},
|
||||
"unknown configuration field `features.foo`",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_rejects_unknown_profile_feature_key() {
|
||||
let path = Path::new("/tmp/config.toml");
|
||||
let contents = r#"
|
||||
[profiles.work.features]
|
||||
foo = true"#;
|
||||
|
||||
let error = config_error_from_ignored_toml_fields::<ConfigToml>(path, contents)
|
||||
.expect("unknown feature error");
|
||||
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigError::new(
|
||||
path.to_path_buf(),
|
||||
TextRange {
|
||||
start: TextPosition { line: 3, column: 1 },
|
||||
end: TextPosition { line: 3, column: 3 },
|
||||
},
|
||||
"unknown configuration field `profiles.work.features.foo`",
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use codex_config::RequirementSource;
|
||||
use codex_config::SessionThreadConfig;
|
||||
use codex_config::StaticThreadConfigLoader;
|
||||
use codex_config::ThreadConfigSource;
|
||||
use codex_config::config_error_from_ignored_toml_fields;
|
||||
use codex_config::config_error_from_toml;
|
||||
use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::config_toml::ProjectConfig;
|
||||
@@ -228,6 +229,143 @@ async fn returns_config_error_for_schema_error_in_user_config() {
|
||||
assert_eq!(config_error, &expected_config_error);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_config_rejects_unknown_user_config_key() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let contents = "model = \"gpt-5\"\nunknown_key = true";
|
||||
let config_path = tmp.path().join(CONFIG_TOML_FILE);
|
||||
std::fs::write(&config_path, contents).expect("write config");
|
||||
|
||||
let err = ConfigBuilder::default()
|
||||
.codex_home(tmp.path().to_path_buf())
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.strict_config(/*strict_config*/ true)
|
||||
.build()
|
||||
.await
|
||||
.expect_err("expected error");
|
||||
|
||||
let config_error = config_error_from_io(&err);
|
||||
let expected_config_error =
|
||||
config_error_from_ignored_toml_fields::<ConfigToml>(&config_path, contents)
|
||||
.expect("unknown field error");
|
||||
assert_eq!(config_error, &expected_config_error);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_config_rejects_unknown_cli_override_key() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
let err = ConfigBuilder::default()
|
||||
.codex_home(tmp.path().to_path_buf())
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.cli_overrides(vec![(
|
||||
"foo".to_string(),
|
||||
TomlValue::String("bar".to_string()),
|
||||
)])
|
||||
.strict_config(/*strict_config*/ true)
|
||||
.build()
|
||||
.await
|
||||
.expect_err("expected error");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"unknown configuration field `foo` in -c/--config override"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_config_rejects_unknown_cli_override_key_with_relative_path_override() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let instructions_path = tmp.path().join("instructions.md");
|
||||
std::fs::write(&instructions_path, "instructions").expect("write instructions");
|
||||
|
||||
let err = ConfigBuilder::default()
|
||||
.codex_home(tmp.path().to_path_buf())
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.cli_overrides(vec![
|
||||
(
|
||||
"model_instructions_file".to_string(),
|
||||
TomlValue::String("instructions.md".to_string()),
|
||||
),
|
||||
("foo".to_string(), TomlValue::String("bar".to_string())),
|
||||
])
|
||||
.strict_config(/*strict_config*/ true)
|
||||
.build()
|
||||
.await
|
||||
.expect_err("expected error");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"unknown configuration field `foo` in -c/--config override"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_config_rejects_unknown_feature_cli_override_key() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
let err = ConfigBuilder::default()
|
||||
.codex_home(tmp.path().to_path_buf())
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.cli_overrides(vec![("features.foo".to_string(), TomlValue::Boolean(true))])
|
||||
.strict_config(/*strict_config*/ true)
|
||||
.build()
|
||||
.await
|
||||
.expect_err("expected error");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"unknown configuration field `features.foo` in -c/--config override"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_config_rejects_unknown_feature_user_config_key() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let contents = "[features]\nfoo = true";
|
||||
let config_path = tmp.path().join(CONFIG_TOML_FILE);
|
||||
std::fs::write(&config_path, contents).expect("write config");
|
||||
|
||||
let err = ConfigBuilder::default()
|
||||
.codex_home(tmp.path().to_path_buf())
|
||||
.fallback_cwd(Some(tmp.path().to_path_buf()))
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.strict_config(/*strict_config*/ true)
|
||||
.build()
|
||||
.await
|
||||
.expect_err("expected error");
|
||||
|
||||
let config_error = config_error_from_io(&err);
|
||||
assert_eq!(
|
||||
config_error.message,
|
||||
"unknown configuration field `features.foo`"
|
||||
);
|
||||
assert_eq!(config_error.range.start.line, 2);
|
||||
assert_eq!(config_error.range.start.column, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_points_to_unknown_nested_key() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let contents = "[mcp_servers.local]\ncommand = \"echo\"\nunknown_key = true";
|
||||
let config_path = tmp.path().join(CONFIG_TOML_FILE);
|
||||
std::fs::write(&config_path, contents).expect("write config");
|
||||
|
||||
let error = config_error_from_ignored_toml_fields::<ConfigToml>(&config_path, contents)
|
||||
.expect("unknown field error");
|
||||
|
||||
assert_eq!(
|
||||
error.message,
|
||||
"unknown configuration field `mcp_servers.local.unknown_key`"
|
||||
);
|
||||
assert_eq!(error.range.start.line, 3);
|
||||
assert_eq!(error.range.start.column, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_error_points_to_feature_value() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
@@ -15,7 +15,6 @@ use codex_config::ConfigRequirements;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_config::ConstrainedWithSource;
|
||||
use codex_config::FeatureRequirementsToml;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::McpServerIdentity;
|
||||
use codex_config::McpServerRequirement;
|
||||
use codex_config::PluginRequirementsToml;
|
||||
@@ -135,9 +134,11 @@ mod otel;
|
||||
mod permissions;
|
||||
#[cfg(test)]
|
||||
mod schema;
|
||||
pub use codex_config::ConfigLoadOptions;
|
||||
pub use codex_config::Constrained;
|
||||
pub use codex_config::ConstraintError;
|
||||
pub use codex_config::ConstraintResult;
|
||||
pub use codex_config::LoaderOverrides;
|
||||
pub use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
pub use codex_sandboxing::system_bwrap_warning;
|
||||
@@ -968,6 +969,7 @@ pub struct ConfigBuilder {
|
||||
cli_overrides: Option<Vec<(String, TomlValue)>>,
|
||||
harness_overrides: Option<ConfigOverrides>,
|
||||
loader_overrides: Option<LoaderOverrides>,
|
||||
strict_config: bool,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
thread_config_loader: Option<Arc<dyn ThreadConfigLoader>>,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
@@ -994,6 +996,11 @@ impl ConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn strict_config(mut self, strict_config: bool) -> Self {
|
||||
self.strict_config = strict_config;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cloud_requirements(mut self, cloud_requirements: CloudRequirementsLoader) -> Self {
|
||||
self.cloud_requirements = cloud_requirements;
|
||||
self
|
||||
@@ -1023,6 +1030,7 @@ impl ConfigBuilder {
|
||||
cli_overrides,
|
||||
harness_overrides,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements,
|
||||
thread_config_loader,
|
||||
fallback_cwd,
|
||||
@@ -1045,7 +1053,10 @@ impl ConfigBuilder {
|
||||
&codex_home,
|
||||
Some(cwd),
|
||||
&cli_overrides,
|
||||
loader_overrides,
|
||||
ConfigLoadOptions {
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
},
|
||||
cloud_requirements,
|
||||
thread_config_loader
|
||||
.as_deref()
|
||||
@@ -1347,35 +1358,28 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// DEPRECATED: Use [Config::load_with_cli_overrides()] instead because working
|
||||
/// with [ConfigToml] directly means that [ConfigRequirements] have not been
|
||||
/// applied yet, which risks failing to enforce required constraints.
|
||||
pub async fn load_config_as_toml_with_cli_overrides(
|
||||
codex_home: &Path,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
) -> std::io::Result<ConfigToml> {
|
||||
load_config_as_toml_with_cli_and_loader_overrides(
|
||||
codex_home,
|
||||
cwd,
|
||||
cli_overrides,
|
||||
LoaderOverrides::default(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn load_config_as_toml_with_cli_and_loader_overrides(
|
||||
codex_home: &Path,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
) -> std::io::Result<ConfigToml> {
|
||||
load_config_as_toml_with_cli_and_load_options(codex_home, cwd, cli_overrides, loader_overrides)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn load_config_as_toml_with_cli_and_load_options(
|
||||
codex_home: &Path,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
options: impl Into<ConfigLoadOptions>,
|
||||
) -> std::io::Result<ConfigToml> {
|
||||
let config_layer_stack = load_config_layers_state(
|
||||
LOCAL_FS.as_ref(),
|
||||
codex_home,
|
||||
cwd.cloned(),
|
||||
&cli_overrides,
|
||||
loader_overrides,
|
||||
options,
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
)
|
||||
|
||||
@@ -8,17 +8,20 @@ use codex_tools::ToolSpec;
|
||||
|
||||
use super::ExecContext;
|
||||
use super::PUBLIC_TOOL_NAME;
|
||||
use super::build_enabled_tools;
|
||||
use super::handle_runtime_response;
|
||||
use super::is_exec_tool_name;
|
||||
|
||||
pub struct CodeModeExecuteHandler {
|
||||
spec: ToolSpec,
|
||||
nested_tool_specs: Vec<ToolSpec>,
|
||||
}
|
||||
|
||||
impl CodeModeExecuteHandler {
|
||||
pub(crate) fn new(spec: ToolSpec) -> Self {
|
||||
Self { spec }
|
||||
pub(crate) fn new(spec: ToolSpec, nested_tool_specs: Vec<ToolSpec>) -> Self {
|
||||
Self {
|
||||
spec,
|
||||
nested_tool_specs,
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
@@ -31,7 +34,8 @@ impl CodeModeExecuteHandler {
|
||||
let args =
|
||||
codex_code_mode::parse_exec_source(&code).map_err(FunctionCallError::RespondToModel)?;
|
||||
let exec = ExecContext { session, turn };
|
||||
let enabled_tools = build_enabled_tools(&exec).await;
|
||||
let enabled_tools =
|
||||
codex_tools::collect_code_mode_tool_definitions(&self.nested_tool_specs);
|
||||
let stored_values = exec
|
||||
.session
|
||||
.services
|
||||
|
||||
@@ -29,12 +29,9 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::router::ToolCall;
|
||||
use crate::tools::router::ToolCallSource;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use crate::tools::router::extension_tool_bundles;
|
||||
use crate::unified_exec::resolve_max_tokens;
|
||||
use codex_features::Feature;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::collect_code_mode_tool_definitions;
|
||||
use codex_utils_output_truncation::TruncationPolicy;
|
||||
use codex_utils_output_truncation::formatted_truncate_text_content_items_with_policy;
|
||||
use codex_utils_output_truncation::truncate_function_output_items_with_policy;
|
||||
@@ -260,36 +257,6 @@ fn truncate_code_mode_result(
|
||||
truncate_function_output_items_with_policy(&items, policy)
|
||||
}
|
||||
|
||||
pub(super) async fn build_enabled_tools(
|
||||
exec: &ExecContext,
|
||||
) -> Vec<codex_code_mode::ToolDefinition> {
|
||||
let router = build_nested_router(exec).await;
|
||||
let specs = router.specs();
|
||||
collect_code_mode_tool_definitions(&specs)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::await_holding_invalid_type,
|
||||
reason = "nested tool router construction reads through the session-owned manager guard"
|
||||
)]
|
||||
async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
|
||||
let nested_tools_config = exec.turn.tools_config.for_code_mode_nested_tools();
|
||||
let mcp_connection_manager = exec.session.services.mcp_connection_manager.read().await;
|
||||
let listed_mcp_tools = mcp_connection_manager.list_all_tools().await;
|
||||
|
||||
ToolRouter::from_config(
|
||||
&nested_tools_config,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: Some(listed_mcp_tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: extension_tool_bundles(exec.session.as_ref()),
|
||||
dynamic_tools: exec.turn.dynamic_tools.as_slice(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async fn call_nested_tool(
|
||||
_exec: ExecContext,
|
||||
tool_runtime: ToolCallRuntime,
|
||||
|
||||
@@ -9,10 +9,13 @@ use crate::tools::registry::ToolHandler;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallRequest;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::protocol::DynamicToolCallResponseEvent;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::dynamic_tool_to_loadable_tool_spec;
|
||||
use serde_json::Value;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -20,11 +23,17 @@ use tracing::warn;
|
||||
|
||||
pub struct DynamicToolHandler {
|
||||
tool_name: ToolName,
|
||||
spec: Option<ToolSpec>,
|
||||
}
|
||||
|
||||
impl DynamicToolHandler {
|
||||
pub fn new(tool_name: ToolName) -> Self {
|
||||
Self { tool_name }
|
||||
pub fn new(tool: &DynamicToolSpec) -> Option<Self> {
|
||||
let tool_name = ToolName::new(tool.namespace.clone(), tool.name.clone());
|
||||
let spec = dynamic_tool_to_loadable_tool_spec(tool).ok()?.into();
|
||||
Some(Self {
|
||||
tool_name,
|
||||
spec: Some(spec),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +44,10 @@ impl ToolHandler for DynamicToolHandler {
|
||||
self.tool_name.clone()
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
self.spec.clone()
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -14,7 +14,11 @@ use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolTelemetryTags;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::mcp_tool_to_responses_api_tool;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -35,6 +39,26 @@ impl ToolHandler for McpHandler {
|
||||
self.tool_info.canonical_tool_name()
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
let tool_name = self.tool_name();
|
||||
let namespace_name = tool_name.namespace.as_ref()?;
|
||||
let tool = mcp_tool_to_responses_api_tool(&tool_name, &self.tool_info.tool).ok()?;
|
||||
let description = self
|
||||
.tool_info
|
||||
.namespace_description
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_default();
|
||||
|
||||
Some(ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace_name.clone(),
|
||||
description,
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(tool)],
|
||||
}))
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.tool_info.supports_parallel_tool_calls
|
||||
}
|
||||
|
||||
@@ -168,7 +168,11 @@ pub(crate) struct PostToolUsePayload {
|
||||
pub(crate) tool_response: Value,
|
||||
}
|
||||
|
||||
trait AnyToolHandler: Send + Sync {
|
||||
pub(crate) trait AnyToolHandler: Send + Sync {
|
||||
fn tool_name(&self) -> ToolName;
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec>;
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool;
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool;
|
||||
@@ -197,6 +201,14 @@ impl<T> AnyToolHandler for T
|
||||
where
|
||||
T: ToolHandler,
|
||||
{
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolHandler::tool_name(self)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
ToolHandler::spec(self)
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
ToolHandler::supports_parallel_tool_calls(self)
|
||||
}
|
||||
@@ -531,24 +543,17 @@ impl ToolRegistry {
|
||||
pub struct ToolRegistryBuilder {
|
||||
handlers: HashMap<ToolName, Arc<dyn AnyToolHandler>>,
|
||||
specs: Vec<ToolSpec>,
|
||||
code_mode_enabled: bool,
|
||||
}
|
||||
|
||||
impl ToolRegistryBuilder {
|
||||
pub fn new(code_mode_enabled: bool) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handlers: HashMap::new(),
|
||||
specs: Vec::new(),
|
||||
code_mode_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_spec(&mut self, spec: ToolSpec) {
|
||||
let spec = if self.code_mode_enabled {
|
||||
codex_tools::augment_tool_spec_for_code_mode(spec)
|
||||
} else {
|
||||
spec
|
||||
};
|
||||
self.specs.push(spec);
|
||||
}
|
||||
|
||||
@@ -556,17 +561,32 @@ impl ToolRegistryBuilder {
|
||||
where
|
||||
H: ToolHandler + 'static,
|
||||
{
|
||||
self.register_any_handler(handler);
|
||||
}
|
||||
|
||||
pub(crate) fn register_any_handler(&mut self, handler: Arc<dyn AnyToolHandler>) {
|
||||
self.register_any_handler_internal(handler, /*include_spec*/ true);
|
||||
}
|
||||
|
||||
pub(crate) fn register_any_handler_without_spec(&mut self, handler: Arc<dyn AnyToolHandler>) {
|
||||
self.register_any_handler_internal(handler, /*include_spec*/ false);
|
||||
}
|
||||
|
||||
fn register_any_handler_internal(
|
||||
&mut self,
|
||||
handler: Arc<dyn AnyToolHandler>,
|
||||
include_spec: bool,
|
||||
) {
|
||||
let name = handler.tool_name();
|
||||
if self.handlers.contains_key(&name) {
|
||||
error_or_panic(format!("handler for tool {name} already registered"));
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(spec) = handler.spec() {
|
||||
if include_spec && let Some(spec) = handler.spec() {
|
||||
self.push_spec(spec);
|
||||
}
|
||||
|
||||
let handler: Arc<dyn AnyToolHandler> = handler;
|
||||
self.handlers.insert(name, handler);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,16 +63,13 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_handler_adds_handler_and_augments_specs_for_code_mode() {
|
||||
let mut builder = ToolRegistryBuilder::new(/*code_mode_enabled*/ true);
|
||||
fn register_handler_adds_handler_and_spec() {
|
||||
let mut builder = ToolRegistryBuilder::new();
|
||||
builder.register_handler(Arc::new(GetGoalHandler));
|
||||
|
||||
let (specs, registry) = builder.build();
|
||||
|
||||
assert_eq!(specs.len(), 1);
|
||||
assert_eq!(
|
||||
specs[0],
|
||||
codex_tools::augment_tool_spec_for_code_mode(create_get_goal_tool())
|
||||
);
|
||||
assert_eq!(specs[0], create_get_goal_tool());
|
||||
assert!(registry.has_handler(&codex_tools::ToolName::plain(GET_GOAL_TOOL_NAME)));
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ pub struct ToolCall {
|
||||
|
||||
pub struct ToolRouter {
|
||||
registry: ToolRegistry,
|
||||
specs: Vec<ToolSpec>,
|
||||
model_visible_specs: Vec<ToolSpec>,
|
||||
}
|
||||
|
||||
@@ -90,15 +89,10 @@ impl ToolRouter {
|
||||
|
||||
Self {
|
||||
registry,
|
||||
specs,
|
||||
model_visible_specs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn specs(&self) -> Vec<ToolSpec> {
|
||||
self.specs.clone()
|
||||
}
|
||||
|
||||
pub fn model_visible_specs(&self) -> Vec<ToolSpec> {
|
||||
self.model_visible_specs.clone()
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ async fn tools_without_handlers_do_not_support_parallel() -> anyhow::Result<()>
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_visible_specs_filter_deferred_dynamic_tools() -> anyhow::Result<()> {
|
||||
async fn specs_filter_deferred_dynamic_tools() -> anyhow::Result<()> {
|
||||
let (_, turn) = make_session_and_context().await;
|
||||
let hidden_tool = "hidden_dynamic_tool";
|
||||
let visible_tool = "visible_dynamic_tool";
|
||||
@@ -273,10 +273,6 @@ async fn model_visible_specs_filter_deferred_dynamic_tools() -> anyhow::Result<(
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
namespace_function_names(&router.specs(), "codex_app"),
|
||||
vec![hidden_tool.to_string(), visible_tool.to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
namespace_function_names(&router.model_visible_specs(), "codex_app"),
|
||||
vec![visible_tool.to_string()]
|
||||
@@ -334,13 +330,6 @@ async fn extension_tool_bundles_are_model_visible_and_dispatchable() -> anyhow::
|
||||
},
|
||||
);
|
||||
|
||||
assert!(
|
||||
router
|
||||
.specs()
|
||||
.iter()
|
||||
.any(|spec| spec.name() == "extension_echo"),
|
||||
"expected extension-provided tool spec to be registered"
|
||||
);
|
||||
assert!(
|
||||
router
|
||||
.model_visible_specs()
|
||||
|
||||
@@ -70,7 +70,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
use crate::tools::tool_search_entry::build_tool_search_entries_for_config;
|
||||
|
||||
let mcp_tool_plan_inputs = mcp_tools.as_deref().map(map_mcp_tools_for_plan);
|
||||
let deferred_mcp_tool_sources = deferred_mcp_tools.as_deref();
|
||||
let default_agent_type_description =
|
||||
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
|
||||
let min_wait_timeout_ms = if config.multi_agent_v2 {
|
||||
@@ -85,12 +84,12 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
DEFAULT_WAIT_TIMEOUT_MS.clamp(min_wait_timeout_ms, MAX_WAIT_TIMEOUT_MS);
|
||||
let deferred_dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading && (config.namespace_tools || tool.namespace.is_none()))
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let tool_search_entries = build_tool_search_entries_for_config(
|
||||
config,
|
||||
deferred_mcp_tool_sources,
|
||||
deferred_mcp_tools.as_deref(),
|
||||
&deferred_dynamic_tools,
|
||||
);
|
||||
let mut builder = build_tool_registry_builder(
|
||||
@@ -99,7 +98,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
mcp_tools: mcp_tool_plan_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| inputs.mcp_tools.as_slice()),
|
||||
deferred_mcp_tools: deferred_mcp_tool_sources,
|
||||
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
|
||||
tool_namespaces: mcp_tool_plan_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| &inputs.tool_namespaces),
|
||||
|
||||
@@ -27,6 +27,7 @@ use crate::tools::handlers::ViewImageHandler;
|
||||
use crate::tools::handlers::WriteStdinHandler;
|
||||
use crate::tools::handlers::agent_jobs::ReportAgentJobResultHandler;
|
||||
use crate::tools::handlers::agent_jobs::SpawnAgentsOnCsvHandler;
|
||||
use crate::tools::handlers::extension_tools::extension_tool_spec;
|
||||
use crate::tools::handlers::multi_agents::CloseAgentHandler;
|
||||
use crate::tools::handlers::multi_agents::ResumeAgentHandler;
|
||||
use crate::tools::handlers::multi_agents::SendInputHandler;
|
||||
@@ -44,12 +45,11 @@ use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
|
||||
use crate::tools::hosted_spec::WebSearchToolOptions;
|
||||
use crate::tools::hosted_spec::create_image_generation_tool;
|
||||
use crate::tools::hosted_spec::create_web_search_tool;
|
||||
use crate::tools::registry::AnyToolHandler;
|
||||
use crate::tools::registry::ToolRegistryBuilder;
|
||||
use crate::tools::spec_plan_types::ToolRegistryBuildParams;
|
||||
use crate::tools::spec_plan_types::agent_type_description;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolEnvironmentMode;
|
||||
use codex_tools::ToolName;
|
||||
@@ -57,12 +57,9 @@ use codex_tools::ToolSearchSource;
|
||||
use codex_tools::ToolSearchSourceInfo;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::coalesce_loadable_tool_specs;
|
||||
use codex_tools::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use codex_tools::collect_tool_search_source_infos;
|
||||
use codex_tools::default_namespace_description;
|
||||
use codex_tools::dynamic_tool_to_loadable_tool_spec;
|
||||
use codex_tools::mcp_tool_to_responses_api_tool;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
@@ -71,8 +68,21 @@ pub fn build_tool_registry_builder(
|
||||
config: &ToolsConfig,
|
||||
params: ToolRegistryBuildParams<'_>,
|
||||
) -> ToolRegistryBuilder {
|
||||
let mut builder = ToolRegistryBuilder::new(config.code_mode_enabled);
|
||||
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
|
||||
let mut builder = ToolRegistryBuilder::new();
|
||||
let all_deferred_tools = params
|
||||
.deferred_mcp_tools
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(codex_mcp::ToolInfo::canonical_tool_name)
|
||||
.chain(
|
||||
params
|
||||
.dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.map(|tool| ToolName::new(tool.namespace.clone(), tool.name.clone())),
|
||||
)
|
||||
.collect::<HashSet<_>>();
|
||||
let handlers = collect_handler_tools(config, params);
|
||||
|
||||
if config.code_mode_enabled {
|
||||
let namespace_descriptions = params
|
||||
@@ -89,16 +99,18 @@ pub fn build_tool_registry_builder(
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let nested_config = config.for_code_mode_nested_tools();
|
||||
let nested_builder = build_tool_registry_builder(
|
||||
&nested_config,
|
||||
ToolRegistryBuildParams {
|
||||
discoverable_tools: None,
|
||||
..params
|
||||
},
|
||||
let mut code_mode_nested_tool_specs = handlers
|
||||
.iter()
|
||||
.filter_map(|handler| handler.spec())
|
||||
.collect::<Vec<_>>();
|
||||
code_mode_nested_tool_specs.extend(
|
||||
params
|
||||
.extension_tool_bundles
|
||||
.iter()
|
||||
.filter_map(|bundle| extension_tool_spec(bundle.spec()).ok()),
|
||||
);
|
||||
let mut enabled_tools =
|
||||
collect_code_mode_exec_prompt_tool_definitions(nested_builder.specs().iter());
|
||||
collect_code_mode_exec_prompt_tool_definitions(code_mode_nested_tool_specs.iter());
|
||||
enabled_tools
|
||||
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
|
||||
builder.register_handler(Arc::new(CodeModeExecuteHandler::new(
|
||||
@@ -106,40 +118,162 @@ pub fn build_tool_registry_builder(
|
||||
&enabled_tools,
|
||||
&namespace_descriptions,
|
||||
config.code_mode_only_enabled,
|
||||
config.search_tool
|
||||
&& params
|
||||
.deferred_mcp_tools
|
||||
.is_some_and(|tools| !tools.is_empty()),
|
||||
config.search_tool && !all_deferred_tools.is_empty(),
|
||||
),
|
||||
code_mode_nested_tool_specs,
|
||||
)));
|
||||
builder.register_handler(Arc::new(CodeModeWaitHandler));
|
||||
}
|
||||
|
||||
let mut non_deferred_specs = Vec::new();
|
||||
for handler in &handlers {
|
||||
let tool_name = handler.tool_name();
|
||||
if !all_deferred_tools.contains(&tool_name)
|
||||
&& let Some(spec) = handler.spec()
|
||||
{
|
||||
non_deferred_specs.push(spec);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||
web_search_mode: config.web_search_mode,
|
||||
web_search_config: config.web_search_config.as_ref(),
|
||||
web_search_tool_type: config.web_search_tool_type,
|
||||
}) {
|
||||
non_deferred_specs.push(web_search_tool);
|
||||
}
|
||||
if config.image_gen_tool {
|
||||
non_deferred_specs.push(create_image_generation_tool("png"));
|
||||
}
|
||||
|
||||
for spec in merge_into_namespaces(non_deferred_specs) {
|
||||
if !config.namespace_tools && matches!(spec, ToolSpec::Namespace(_)) {
|
||||
continue;
|
||||
}
|
||||
let spec = if config.code_mode_enabled {
|
||||
codex_tools::augment_tool_spec_for_code_mode(spec)
|
||||
} else {
|
||||
spec
|
||||
};
|
||||
builder.push_spec(spec);
|
||||
}
|
||||
|
||||
for handler in handlers {
|
||||
builder.register_any_handler_without_spec(handler);
|
||||
}
|
||||
|
||||
if config.search_tool && config.namespace_tools && !all_deferred_tools.is_empty() {
|
||||
let mut search_source_infos = params
|
||||
.deferred_mcp_tools
|
||||
.map(|mcp_tools| {
|
||||
collect_tool_search_source_infos(mcp_tools.iter().map(|tool| ToolSearchSource {
|
||||
server_name: tool.server_name.as_str(),
|
||||
connector_name: tool.connector_name.as_deref(),
|
||||
description: tool.namespace_description.as_deref(),
|
||||
}))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if params.dynamic_tools.iter().any(|tool| {
|
||||
all_deferred_tools.contains(&ToolName::new(tool.namespace.clone(), tool.name.clone()))
|
||||
}) {
|
||||
search_source_infos.push(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(ToolSearchHandler::new(
|
||||
params.tool_search_entries.to_vec(),
|
||||
search_source_infos,
|
||||
)));
|
||||
}
|
||||
|
||||
for bundle in params.extension_tool_bundles.iter().cloned() {
|
||||
builder.register_tool_bundle(bundle);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
fn merge_into_namespaces(specs: Vec<ToolSpec>) -> Vec<ToolSpec> {
|
||||
let mut merged_specs = Vec::with_capacity(specs.len());
|
||||
let mut namespace_indices = BTreeMap::<String, usize>::new();
|
||||
for spec in specs {
|
||||
match spec {
|
||||
ToolSpec::Namespace(mut namespace) => {
|
||||
if let Some(index) = namespace_indices.get(&namespace.name).copied() {
|
||||
let ToolSpec::Namespace(existing_namespace) = &mut merged_specs[index] else {
|
||||
unreachable!("namespace index must point to a namespace spec");
|
||||
};
|
||||
if existing_namespace.description.trim().is_empty()
|
||||
&& !namespace.description.trim().is_empty()
|
||||
{
|
||||
existing_namespace.description = namespace.description;
|
||||
}
|
||||
existing_namespace.tools.append(&mut namespace.tools);
|
||||
continue;
|
||||
}
|
||||
|
||||
namespace_indices.insert(namespace.name.clone(), merged_specs.len());
|
||||
merged_specs.push(ToolSpec::Namespace(namespace));
|
||||
}
|
||||
spec => merged_specs.push(spec),
|
||||
}
|
||||
}
|
||||
|
||||
for spec in &mut merged_specs {
|
||||
let ToolSpec::Namespace(namespace) = spec else {
|
||||
continue;
|
||||
};
|
||||
|
||||
namespace.tools.sort_by(|left, right| match (left, right) {
|
||||
(
|
||||
ResponsesApiNamespaceTool::Function(left),
|
||||
ResponsesApiNamespaceTool::Function(right),
|
||||
) => left.name.cmp(&right.name),
|
||||
});
|
||||
|
||||
if namespace.description.trim().is_empty() {
|
||||
namespace.description = default_namespace_description(&namespace.name);
|
||||
}
|
||||
}
|
||||
|
||||
merged_specs
|
||||
}
|
||||
|
||||
fn collect_handler_tools(
|
||||
config: &ToolsConfig,
|
||||
params: ToolRegistryBuildParams<'_>,
|
||||
) -> Vec<Arc<dyn AnyToolHandler>> {
|
||||
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
|
||||
let mut handlers = Vec::<Arc<dyn AnyToolHandler>>::new();
|
||||
|
||||
if config.environment_mode.has_environment() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
match &config.shell_type {
|
||||
ConfigShellToolType::Default => {
|
||||
builder.register_handler(Arc::new(ShellHandler::new(ShellToolOptions {
|
||||
handlers.push(Arc::new(ShellHandler::new(ShellToolOptions {
|
||||
exec_permission_approvals_enabled,
|
||||
})));
|
||||
}
|
||||
ConfigShellToolType::Local => {
|
||||
builder.register_handler(Arc::new(LocalShellHandler::new()));
|
||||
handlers.push(Arc::new(LocalShellHandler::new()));
|
||||
}
|
||||
ConfigShellToolType::UnifiedExec => {
|
||||
builder.register_handler(Arc::new(ExecCommandHandler::new(
|
||||
handlers.push(Arc::new(ExecCommandHandler::new(
|
||||
ExecCommandHandlerOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
include_environment_id,
|
||||
},
|
||||
)));
|
||||
builder.register_handler(Arc::new(WriteStdinHandler));
|
||||
handlers.push(Arc::new(WriteStdinHandler));
|
||||
}
|
||||
ConfigShellToolType::Disabled => {}
|
||||
ConfigShellToolType::ShellCommand => {
|
||||
builder.register_handler(Arc::new(ShellCommandHandler::new(
|
||||
handlers.push(Arc::new(ShellCommandHandler::new(
|
||||
ShellCommandHandlerOptions {
|
||||
backend_config: config.shell_command_backend,
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
@@ -155,101 +289,62 @@ pub fn build_tool_registry_builder(
|
||||
{
|
||||
match &config.shell_type {
|
||||
ConfigShellToolType::Default => {
|
||||
builder.register_handler(Arc::new(ContainerExecHandler));
|
||||
builder.register_handler(Arc::new(LocalShellHandler::default()));
|
||||
builder.register_handler(Arc::new(ShellCommandHandler::from(
|
||||
handlers.push(Arc::new(ContainerExecHandler));
|
||||
handlers.push(Arc::new(LocalShellHandler::default()));
|
||||
handlers.push(Arc::new(ShellCommandHandler::from(
|
||||
config.shell_command_backend,
|
||||
)));
|
||||
}
|
||||
ConfigShellToolType::Local => {
|
||||
builder.register_handler(Arc::new(ShellHandler::default()));
|
||||
builder.register_handler(Arc::new(ContainerExecHandler));
|
||||
builder.register_handler(Arc::new(ShellCommandHandler::from(
|
||||
handlers.push(Arc::new(ShellHandler::default()));
|
||||
handlers.push(Arc::new(ContainerExecHandler));
|
||||
handlers.push(Arc::new(ShellCommandHandler::from(
|
||||
config.shell_command_backend,
|
||||
)));
|
||||
}
|
||||
ConfigShellToolType::UnifiedExec => {
|
||||
builder.register_handler(Arc::new(ShellHandler::default()));
|
||||
builder.register_handler(Arc::new(ContainerExecHandler));
|
||||
builder.register_handler(Arc::new(LocalShellHandler::default()));
|
||||
builder.register_handler(Arc::new(ShellCommandHandler::from(
|
||||
handlers.push(Arc::new(ShellHandler::default()));
|
||||
handlers.push(Arc::new(ContainerExecHandler));
|
||||
handlers.push(Arc::new(LocalShellHandler::default()));
|
||||
handlers.push(Arc::new(ShellCommandHandler::from(
|
||||
config.shell_command_backend,
|
||||
)));
|
||||
}
|
||||
ConfigShellToolType::ShellCommand => {
|
||||
builder.register_handler(Arc::new(ShellHandler::default()));
|
||||
builder.register_handler(Arc::new(ContainerExecHandler));
|
||||
builder.register_handler(Arc::new(LocalShellHandler::default()));
|
||||
handlers.push(Arc::new(ShellHandler::default()));
|
||||
handlers.push(Arc::new(ContainerExecHandler));
|
||||
handlers.push(Arc::new(LocalShellHandler::default()));
|
||||
}
|
||||
ConfigShellToolType::Disabled => {}
|
||||
}
|
||||
}
|
||||
|
||||
if params.mcp_tools.is_some() {
|
||||
builder.register_handler(Arc::new(ListMcpResourcesHandler));
|
||||
builder.register_handler(Arc::new(ListMcpResourceTemplatesHandler));
|
||||
builder.register_handler(Arc::new(ReadMcpResourceHandler));
|
||||
handlers.push(Arc::new(ListMcpResourcesHandler));
|
||||
handlers.push(Arc::new(ListMcpResourceTemplatesHandler));
|
||||
handlers.push(Arc::new(ReadMcpResourceHandler));
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(PlanHandler));
|
||||
handlers.push(Arc::new(PlanHandler));
|
||||
if config.goal_tools {
|
||||
builder.register_handler(Arc::new(GetGoalHandler));
|
||||
builder.register_handler(Arc::new(CreateGoalHandler));
|
||||
builder.register_handler(Arc::new(UpdateGoalHandler));
|
||||
handlers.push(Arc::new(GetGoalHandler));
|
||||
handlers.push(Arc::new(CreateGoalHandler));
|
||||
handlers.push(Arc::new(UpdateGoalHandler));
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(RequestUserInputHandler {
|
||||
handlers.push(Arc::new(RequestUserInputHandler {
|
||||
available_modes: config.request_user_input_available_modes.clone(),
|
||||
}));
|
||||
|
||||
if config.request_permissions_tool_enabled {
|
||||
builder.register_handler(Arc::new(RequestPermissionsHandler));
|
||||
}
|
||||
|
||||
let deferred_dynamic_tools = params
|
||||
.dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading && (config.namespace_tools || tool.namespace.is_none()))
|
||||
.collect::<Vec<_>>();
|
||||
let deferred_mcp_tools_for_search = if config.namespace_tools {
|
||||
params.deferred_mcp_tools
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if config.search_tool
|
||||
&& (deferred_mcp_tools_for_search.is_some() || !deferred_dynamic_tools.is_empty())
|
||||
{
|
||||
let mut search_source_infos = deferred_mcp_tools_for_search
|
||||
.map(|deferred_mcp_tools| {
|
||||
collect_tool_search_source_infos(deferred_mcp_tools.iter().map(|tool| {
|
||||
ToolSearchSource {
|
||||
server_name: tool.server_name.as_str(),
|
||||
connector_name: tool.connector_name.as_deref(),
|
||||
description: tool.namespace_description.as_deref(),
|
||||
}
|
||||
}))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if !deferred_dynamic_tools.is_empty() {
|
||||
search_source_infos.push(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(ToolSearchHandler::new(
|
||||
params.tool_search_entries.to_vec(),
|
||||
search_source_infos,
|
||||
)));
|
||||
handlers.push(Arc::new(RequestPermissionsHandler));
|
||||
}
|
||||
|
||||
if config.tool_suggest
|
||||
&& let Some(discoverable_tools) =
|
||||
params.discoverable_tools.filter(|tools| !tools.is_empty())
|
||||
{
|
||||
builder.register_handler(Arc::new(RequestPluginInstallHandler::new(
|
||||
handlers.push(Arc::new(RequestPluginInstallHandler::new(
|
||||
discoverable_tools,
|
||||
)));
|
||||
}
|
||||
@@ -257,7 +352,7 @@ pub fn build_tool_registry_builder(
|
||||
if config.environment_mode.has_environment() && config.apply_patch_tool_type.is_some() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
builder.register_handler(Arc::new(ApplyPatchHandler::new(include_environment_id)));
|
||||
handlers.push(Arc::new(ApplyPatchHandler::new(include_environment_id)));
|
||||
}
|
||||
|
||||
if config
|
||||
@@ -265,25 +360,13 @@ pub fn build_tool_registry_builder(
|
||||
.iter()
|
||||
.any(|tool| tool == "test_sync_tool")
|
||||
{
|
||||
builder.register_handler(Arc::new(TestSyncHandler));
|
||||
}
|
||||
|
||||
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||
web_search_mode: config.web_search_mode,
|
||||
web_search_config: config.web_search_config.as_ref(),
|
||||
web_search_tool_type: config.web_search_tool_type,
|
||||
}) {
|
||||
builder.push_spec(web_search_tool);
|
||||
}
|
||||
|
||||
if config.image_gen_tool {
|
||||
builder.push_spec(create_image_generation_tool("png"));
|
||||
handlers.push(Arc::new(TestSyncHandler));
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
builder.register_handler(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
|
||||
handlers.push(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
|
||||
can_request_original_image_detail: config.can_request_original_image_detail,
|
||||
include_environment_id,
|
||||
})));
|
||||
@@ -293,7 +376,7 @@ pub fn build_tool_registry_builder(
|
||||
if config.multi_agent_v2 {
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
builder.register_handler(Arc::new(SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
|
||||
handlers.push(Arc::new(SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
|
||||
available_models: config.available_models.clone(),
|
||||
agent_type_description,
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
@@ -301,17 +384,17 @@ pub fn build_tool_registry_builder(
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
|
||||
})));
|
||||
builder.register_handler(Arc::new(SendMessageHandlerV2));
|
||||
builder.register_handler(Arc::new(FollowupTaskHandlerV2));
|
||||
builder.register_handler(Arc::new(WaitAgentHandlerV2::new(
|
||||
handlers.push(Arc::new(SendMessageHandlerV2));
|
||||
handlers.push(Arc::new(FollowupTaskHandlerV2));
|
||||
handlers.push(Arc::new(WaitAgentHandlerV2::new(
|
||||
params.wait_agent_timeouts,
|
||||
)));
|
||||
builder.register_handler(Arc::new(CloseAgentHandlerV2));
|
||||
builder.register_handler(Arc::new(ListAgentsHandlerV2));
|
||||
handlers.push(Arc::new(CloseAgentHandlerV2));
|
||||
handlers.push(Arc::new(ListAgentsHandlerV2));
|
||||
} else {
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
builder.register_handler(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
|
||||
handlers.push(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
|
||||
available_models: config.available_models.clone(),
|
||||
agent_type_description,
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
@@ -319,122 +402,45 @@ pub fn build_tool_registry_builder(
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
|
||||
})));
|
||||
builder.register_handler(Arc::new(SendInputHandler));
|
||||
builder.register_handler(Arc::new(ResumeAgentHandler));
|
||||
builder.register_handler(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
|
||||
builder.register_handler(Arc::new(CloseAgentHandler));
|
||||
handlers.push(Arc::new(SendInputHandler));
|
||||
handlers.push(Arc::new(ResumeAgentHandler));
|
||||
handlers.push(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
|
||||
handlers.push(Arc::new(CloseAgentHandler));
|
||||
}
|
||||
}
|
||||
|
||||
if config.agent_jobs_tools {
|
||||
builder.register_handler(Arc::new(SpawnAgentsOnCsvHandler));
|
||||
handlers.push(Arc::new(SpawnAgentsOnCsvHandler));
|
||||
if config.agent_jobs_worker_tools {
|
||||
builder.register_handler(Arc::new(ReportAgentJobResultHandler));
|
||||
handlers.push(Arc::new(ReportAgentJobResultHandler));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mcp_tools) = params.mcp_tools {
|
||||
let mut entries = mcp_tools
|
||||
.iter()
|
||||
.map(|tool| (tool.canonical_tool_name(), tool))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||
let mut namespace_entries = BTreeMap::new();
|
||||
|
||||
for (tool_name, tool) in entries {
|
||||
let Some(namespace) = tool_name.namespace.as_ref() else {
|
||||
tracing::error!("Skipping MCP tool `{tool_name}`: MCP tools must be namespaced");
|
||||
continue;
|
||||
};
|
||||
namespace_entries
|
||||
.entry(namespace.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((tool_name, tool));
|
||||
}
|
||||
|
||||
for (namespace, mut entries) in namespace_entries {
|
||||
entries.sort_by_key(|(tool_name, _)| tool_name.name.clone());
|
||||
let tool_namespace = params
|
||||
.tool_namespaces
|
||||
.and_then(|namespaces| namespaces.get(&namespace));
|
||||
let description = tool_namespace
|
||||
.and_then(|namespace| namespace.description.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| {
|
||||
let namespace_name = tool_namespace
|
||||
.map(|namespace| namespace.name.as_str())
|
||||
.unwrap_or(namespace.as_str());
|
||||
default_namespace_description(namespace_name)
|
||||
});
|
||||
let mut tools = Vec::new();
|
||||
for (tool_name, tool) in entries {
|
||||
match mcp_tool_to_responses_api_tool(&tool_name, &tool.tool) {
|
||||
Ok(converted_tool) => {
|
||||
tools.push(ResponsesApiNamespaceTool::Function(converted_tool));
|
||||
builder.register_handler(Arc::new(McpHandler::new(tool.clone())));
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"Failed to convert `{tool_name}` MCP tool to OpenAI tool: {error:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.namespace_tools && !tools.is_empty() {
|
||||
builder.push_spec(ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace,
|
||||
description,
|
||||
tools,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dynamic_tool_specs = Vec::new();
|
||||
for tool in params.dynamic_tools {
|
||||
match dynamic_tool_to_loadable_tool_spec(tool) {
|
||||
Ok(loadable_tool) => {
|
||||
let handler_name = ToolName::new(tool.namespace.clone(), tool.name.clone());
|
||||
dynamic_tool_specs.push(loadable_tool);
|
||||
builder.register_handler(Arc::new(DynamicToolHandler::new(handler_name)));
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"Failed to convert dynamic tool {:?} to OpenAI tool: {error:?}",
|
||||
tool.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for spec in coalesce_loadable_tool_specs(dynamic_tool_specs) {
|
||||
let spec = spec.into();
|
||||
if config.namespace_tools || !matches!(spec, ToolSpec::Namespace(_)) {
|
||||
builder.push_spec(spec);
|
||||
for tool in mcp_tools {
|
||||
handlers.push(Arc::new(McpHandler::new(tool.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(deferred_mcp_tools) = params.deferred_mcp_tools {
|
||||
let directly_registered_mcp_tools = params
|
||||
.mcp_tools
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(ToolInfo::canonical_tool_name)
|
||||
.collect::<HashSet<_>>();
|
||||
for tool in deferred_mcp_tools {
|
||||
if !directly_registered_mcp_tools.contains(&tool.canonical_tool_name()) {
|
||||
builder.register_handler(Arc::new(McpHandler::new(tool.clone())));
|
||||
}
|
||||
handlers.push(Arc::new(McpHandler::new(tool.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
for bundle in params.extension_tool_bundles.iter().cloned() {
|
||||
builder.register_tool_bundle(bundle);
|
||||
for tool in params.dynamic_tools {
|
||||
let Some(handler) = DynamicToolHandler::new(tool).map(Arc::new) else {
|
||||
tracing::error!(
|
||||
"Failed to convert dynamic tool {:?} to OpenAI tool",
|
||||
tool.name
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
handlers.push(handler);
|
||||
}
|
||||
|
||||
builder
|
||||
handlers
|
||||
}
|
||||
|
||||
fn compare_code_mode_tools(
|
||||
|
||||
@@ -1579,20 +1579,7 @@ fn search_tool_description_lists_each_mcp_source_once() {
|
||||
|
||||
let (tools, registry) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([
|
||||
(
|
||||
ToolName::namespaced("mcp__codex_apps__calendar", "_create_event"),
|
||||
mcp_tool(
|
||||
"calendar_create_event",
|
||||
"Create calendar event",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
),
|
||||
(
|
||||
ToolName::namespaced("mcp__rmcp__", "echo"),
|
||||
mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})),
|
||||
),
|
||||
])),
|
||||
/*mcp_tools*/ None,
|
||||
Some(vec![
|
||||
deferred_mcp_tool(
|
||||
"_create_event",
|
||||
@@ -1810,32 +1797,15 @@ fn search_tool_registers_for_deferred_dynamic_tools() {
|
||||
panic!("expected tool_search tool");
|
||||
};
|
||||
assert!(description.contains("- Dynamic tools: Tools provided by the current Codex thread."));
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME, "codex_app"]);
|
||||
assert_eq!(
|
||||
tools
|
||||
.iter()
|
||||
.filter(|tool| tool.name() == "codex_app")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
namespace_function_names(&tools, "codex_app"),
|
||||
vec![
|
||||
"automation_update".to_string(),
|
||||
"automation_list".to_string()
|
||||
]
|
||||
);
|
||||
for tool_name in ["automation_update", "automation_list"] {
|
||||
let dynamic_tool = find_namespace_function_tool(&tools, "codex_app", tool_name);
|
||||
assert_eq!(dynamic_tool.defer_loading, Some(true));
|
||||
}
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]);
|
||||
assert_lacks_tool_name(&tools, "codex_app");
|
||||
assert!(registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
|
||||
assert!(registry.has_handler(&ToolName::namespaced("codex_app", "automation_update")));
|
||||
assert!(registry.has_handler(&ToolName::namespaced("codex_app", "automation_list")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_keeps_plain_deferred_dynamic_tools_when_namespace_tools_are_disabled() {
|
||||
fn search_tool_is_hidden_for_deferred_dynamic_tools_when_namespace_tools_are_disabled() {
|
||||
let model_info = search_capable_model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ToolSearch);
|
||||
@@ -1875,9 +1845,12 @@ fn search_tool_keeps_plain_deferred_dynamic_tools_when_namespace_tools_are_disab
|
||||
&dynamic_tools,
|
||||
);
|
||||
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME, "plain_dynamic"]);
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
assert_lacks_tool_name(&tools, "codex_app");
|
||||
assert!(registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
|
||||
assert_lacks_tool_name(&tools, "plain_dynamic");
|
||||
assert!(!registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
|
||||
assert!(registry.has_handler(&ToolName::namespaced("codex_app", "automation_update")));
|
||||
assert!(registry.has_handler(&ToolName::plain("plain_dynamic")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -365,7 +365,7 @@ async fn assert_model_tools(
|
||||
.iter()
|
||||
.map(ToolSpec::name)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(&tool_names, &expected_tools,);
|
||||
assert_eq!(&tool_names, &expected_tools);
|
||||
}
|
||||
|
||||
async fn assert_default_model_tools(
|
||||
@@ -396,14 +396,14 @@ async fn test_build_specs_gpt5_codex_default() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -421,14 +421,14 @@ async fn test_build_specs_gpt51_codex_default() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -448,14 +448,14 @@ async fn test_build_specs_gpt5_codex_unified_exec_web_search() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -475,14 +475,14 @@ async fn test_build_specs_gpt51_codex_unified_exec_web_search() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -500,14 +500,14 @@ async fn test_gpt_5_1_codex_max_defaults() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -525,14 +525,14 @@ async fn test_codex_5_1_mini_defaults() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -550,14 +550,14 @@ async fn test_gpt_5_defaults() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -575,14 +575,14 @@ async fn test_gpt_5_1_defaults() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -602,14 +602,14 @@ async fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
"image_generation",
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -841,7 +841,7 @@ async fn request_plugin_install_requires_apps_and_plugins_features() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search_tool_description_handles_no_enabled_mcp_tools() {
|
||||
async fn search_tool_is_hidden_without_deferred_tools() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
@@ -865,13 +865,11 @@ async fn search_tool_description_handles_no_enabled_mcp_tools() {
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
let ToolSpec::ToolSearch { description, .. } = search_tool else {
|
||||
panic!("expected tool_search tool");
|
||||
};
|
||||
|
||||
assert!(description.contains("None currently enabled."));
|
||||
assert!(!description.contains("{{source_descriptions}}"));
|
||||
assert!(
|
||||
!tools
|
||||
.iter()
|
||||
.any(|tool| tool.name() == TOOL_SEARCH_TOOL_NAME)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1003,7 +1001,7 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_search_entries_skip_namespace_outputs_when_namespace_tools_are_disabled() {
|
||||
async fn tool_search_entries_skip_mcp_outputs_when_namespace_tools_are_disabled() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ToolSearch);
|
||||
@@ -1048,11 +1046,15 @@ async fn tool_search_entries_skip_namespace_outputs_when_namespace_tools_are_dis
|
||||
.map(|entry| entry.output)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(outputs.len(), 1);
|
||||
match &outputs[0] {
|
||||
LoadableToolSpec::Function(tool) => assert_eq!(tool.name, "plain_dynamic"),
|
||||
LoadableToolSpec::Namespace(_) => panic!("namespace tool_search output should be hidden"),
|
||||
}
|
||||
assert_eq!(outputs.len(), 2);
|
||||
assert!(outputs.iter().any(|output| match output {
|
||||
LoadableToolSpec::Namespace(namespace) => namespace.name == "codex_app",
|
||||
LoadableToolSpec::Function(_) => false,
|
||||
}));
|
||||
assert!(outputs.iter().any(|output| match output {
|
||||
LoadableToolSpec::Function(tool) => tool.name == "plain_dynamic",
|
||||
LoadableToolSpec::Namespace(_) => false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -63,11 +63,7 @@ pub(crate) fn build_tool_search_entries_for_config(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| config.namespace_tools || tool.namespace.is_none())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let dynamic_tools = dynamic_tools.to_vec();
|
||||
build_tool_search_entries(mcp_tools, &dynamic_tools)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,13 +179,13 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
||||
"update_plan",
|
||||
"request_user_input",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"web_search",
|
||||
]);
|
||||
let body0 = req1.single_request().body_json();
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
|
||||
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
|
||||
#[arg(long = "strict-config", global = true, default_value_t = false)]
|
||||
pub strict_config: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub shared: ExecSharedCliOptions,
|
||||
|
||||
@@ -257,7 +261,7 @@ impl FromArgMatches for ResumeArgs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ReviewArgs {
|
||||
/// Review staged, unstaged, and untracked changes.
|
||||
#[arg(
|
||||
|
||||
@@ -52,6 +52,7 @@ use codex_app_server_protocol::TurnStartedNotification;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_cloud_requirements::cloud_requirements_loader_for_storage;
|
||||
use codex_config::ConfigLoadError;
|
||||
use codex_config::ConfigLoadOptions;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::format_config_error_with_source;
|
||||
use codex_core::StateDbHandle;
|
||||
@@ -60,7 +61,7 @@ use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_config_as_toml_with_cli_and_loader_overrides;
|
||||
use codex_core::config::load_config_as_toml_with_cli_and_load_options;
|
||||
use codex_core::config::resolve_oss_provider;
|
||||
use codex_core::find_thread_meta_by_name_str;
|
||||
use codex_core::format_exec_policy_error_with_source;
|
||||
@@ -241,6 +242,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
|
||||
let Cli {
|
||||
command,
|
||||
strict_config,
|
||||
shared,
|
||||
skip_git_repo_check,
|
||||
ephemeral,
|
||||
@@ -317,18 +319,20 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
let loader_overrides = LoaderOverrides {
|
||||
ignore_user_config,
|
||||
ignore_user_and_project_exec_policy_rules: ignore_rules,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config_toml = match load_config_as_toml_with_cli_and_loader_overrides(
|
||||
let config_toml = match load_config_as_toml_with_cli_and_load_options(
|
||||
&codex_home,
|
||||
Some(&config_cwd),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
ConfigLoadOptions {
|
||||
loader_overrides: loader_overrides.clone(),
|
||||
strict_config,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -430,6 +434,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
.cli_overrides(cli_kv_overrides)
|
||||
.harness_overrides(overrides)
|
||||
.loader_overrides(loader_overrides)
|
||||
.strict_config(strict_config)
|
||||
.cloud_requirements(cloud_requirements)
|
||||
.build()
|
||||
.await?;
|
||||
@@ -521,6 +526,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
config: std::sync::Arc::new(config.clone()),
|
||||
cli_overrides: run_cli_overrides,
|
||||
loader_overrides: run_loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements: run_cloud_requirements,
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
|
||||
@@ -32,8 +32,7 @@ fn main() -> anyhow::Result<()> {
|
||||
let mut inner = top_cli.inner;
|
||||
inner
|
||||
.config_overrides
|
||||
.raw_overrides
|
||||
.splice(0..0, top_cli.config_overrides.raw_overrides);
|
||||
.prepend_root_overrides(top_cli.config_overrides);
|
||||
|
||||
run_main(inner, arg0_paths).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -7,6 +7,7 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
|
||||
let cli = TopCli::parse_from([
|
||||
"codex-exec",
|
||||
"resume",
|
||||
"--strict-config",
|
||||
"--last",
|
||||
"--json",
|
||||
"--model",
|
||||
@@ -17,8 +18,12 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
|
||||
"--skip-git-repo-check",
|
||||
PROMPT,
|
||||
]);
|
||||
let mut inner = cli.inner;
|
||||
inner
|
||||
.config_overrides
|
||||
.prepend_root_overrides(cli.config_overrides);
|
||||
|
||||
let Some(codex_exec::Command::Resume(args)) = cli.inner.command else {
|
||||
let Some(codex_exec::Command::Resume(args)) = inner.command.as_ref() else {
|
||||
panic!("expected resume command");
|
||||
};
|
||||
let effective_prompt = args.prompt.clone().or_else(|| {
|
||||
@@ -29,9 +34,10 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
|
||||
}
|
||||
});
|
||||
assert_eq!(effective_prompt.as_deref(), Some(PROMPT));
|
||||
assert_eq!(cli.config_overrides.raw_overrides.len(), 1);
|
||||
assert_eq!(inner.config_overrides.raw_overrides.len(), 1);
|
||||
assert_eq!(
|
||||
cli.config_overrides.raw_overrides[0],
|
||||
inner.config_overrides.raw_overrides[0],
|
||||
"reasoning_level=xhigh"
|
||||
);
|
||||
assert!(inner.strict_config);
|
||||
}
|
||||
|
||||
@@ -231,7 +231,8 @@ pub enum Feature {
|
||||
TuiAppServer,
|
||||
/// Prevent idle system sleep while a turn is actively running.
|
||||
PreventIdleSleep,
|
||||
/// Enable workspace-specific owner nudge copy and prompts in the TUI.
|
||||
/// Removed compatibility flag retained as a no-op now that workspace owner
|
||||
/// usage nudges are always enabled.
|
||||
WorkspaceOwnerUsageNudge,
|
||||
/// Legacy rollout flag for Responses API WebSocket transport experiments.
|
||||
ResponsesWebsockets,
|
||||
@@ -1153,7 +1154,7 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
FeatureSpec {
|
||||
id: Feature::WorkspaceOwnerUsageNudge,
|
||||
key: "workspace_owner_usage_nudge",
|
||||
stage: Stage::UnderDevelopment,
|
||||
stage: Stage::Removed,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::io::Result as IoResult;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::resolve_installation_id;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
@@ -59,6 +59,7 @@ type IncomingMessage = JsonRpcMessage<ClientRequest, Value, ClientNotification>;
|
||||
pub async fn run_main(
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
strict_config: bool,
|
||||
) -> IoResult<()> {
|
||||
// Parse CLI overrides once and derive the base Config eagerly so later
|
||||
// components do not need to work with raw TOML values.
|
||||
@@ -68,7 +69,10 @@ pub async fn run_main(
|
||||
format!("error parsing -c overrides: {e}"),
|
||||
)
|
||||
})?;
|
||||
let config = Config::load_with_cli_overrides(cli_kv_overrides)
|
||||
let config = ConfigBuilder::default()
|
||||
.cli_overrides(cli_kv_overrides)
|
||||
.strict_config(strict_config)
|
||||
.build()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
|
||||
|
||||
@@ -5,7 +5,12 @@ use codex_utils_cli::CliConfigOverrides;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
|
||||
run_main(arg0_paths, CliConfigOverrides::default()).await?;
|
||||
run_main(
|
||||
arg0_paths,
|
||||
CliConfigOverrides::default(),
|
||||
/*strict_config*/ false,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2892,18 +2892,7 @@ impl ChatWidget {
|
||||
self.maybe_send_next_queued_input();
|
||||
}
|
||||
|
||||
fn workspace_owner_usage_nudge_enabled(&self) -> bool {
|
||||
self.config
|
||||
.features
|
||||
.enabled(Feature::WorkspaceOwnerUsageNudge)
|
||||
}
|
||||
|
||||
fn on_rate_limit_error(&mut self, error_kind: RateLimitErrorKind, message: String) {
|
||||
if !self.workspace_owner_usage_nudge_enabled() {
|
||||
self.on_error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
let rate_limit_reached_type = self.codex_rate_limit_reached_type.map(|kind| {
|
||||
if matches!(error_kind, RateLimitErrorKind::UsageLimit) {
|
||||
match kind {
|
||||
@@ -6921,9 +6910,7 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn open_workspace_owner_nudge_prompt(&mut self, credit_type: AddCreditsNudgeCreditType) {
|
||||
if !self.workspace_owner_usage_nudge_enabled()
|
||||
|| self.add_credits_nudge_email_in_flight.is_some()
|
||||
{
|
||||
if self.add_credits_nudge_email_in_flight.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6971,10 +6958,6 @@ impl ChatWidget {
|
||||
&mut self,
|
||||
credit_type: AddCreditsNudgeCreditType,
|
||||
) -> bool {
|
||||
if !self.workspace_owner_usage_nudge_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.add_credits_nudge_email_in_flight = Some(credit_type);
|
||||
true
|
||||
}
|
||||
@@ -6987,9 +6970,6 @@ impl ChatWidget {
|
||||
.add_credits_nudge_email_in_flight
|
||||
.take()
|
||||
.unwrap_or(AddCreditsNudgeCreditType::Credits);
|
||||
if !self.workspace_owner_usage_nudge_enabled() {
|
||||
return;
|
||||
}
|
||||
let message = match (credit_type, result) {
|
||||
(AddCreditsNudgeCreditType::Credits, Ok(AddCreditsNudgeEmailStatus::Sent)) => {
|
||||
"Workspace owner notified."
|
||||
|
||||
@@ -797,57 +797,9 @@ async fn rate_limit_switch_prompt_popup_snapshot() {
|
||||
assert_chatwidget_snapshot!("rate_limit_switch_prompt_popup", popup);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_owner_usage_nudge_flag_disabled_keeps_generic_rate_limit_error() {
|
||||
{
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type =
|
||||
Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
|
||||
chat.on_rate_limit_error(
|
||||
RateLimitErrorKind::UsageLimit,
|
||||
"Usage limit reached.".to_string(),
|
||||
);
|
||||
let rendered = drain_insert_history(&mut rx)
|
||||
.into_iter()
|
||||
.map(|lines| lines_to_single_string(&lines))
|
||||
.collect::<String>();
|
||||
assert!(
|
||||
rendered.contains("Usage limit reached."),
|
||||
"rendered: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type =
|
||||
Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
|
||||
chat.on_rate_limit_error(
|
||||
RateLimitErrorKind::UsageLimit,
|
||||
"Usage limit reached.".to_string(),
|
||||
);
|
||||
let popup = render_bottom_popup(&chat, /*width*/ 100);
|
||||
assert!(
|
||||
!popup.contains("Request a limit increase from your owner"),
|
||||
"popup: {popup}"
|
||||
);
|
||||
assert_no_owner_nudge_or_rate_limit_refresh(&mut rx);
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_workspace_owner_usage_nudge(chat: &mut ChatWidget) {
|
||||
chat.set_feature_enabled(Feature::WorkspaceOwnerUsageNudge, /*enabled*/ true);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_member_credits_depleted_prompts_and_sends_credits() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -867,7 +819,6 @@ async fn workspace_member_credits_depleted_prompts_and_sends_credits() {
|
||||
#[tokio::test]
|
||||
async fn workspace_member_usage_limit_prompts_and_sends_usage_limit() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -887,7 +838,6 @@ async fn workspace_member_usage_limit_prompts_and_sends_usage_limit() {
|
||||
#[tokio::test]
|
||||
async fn header_rate_limit_snapshot_preserves_member_limit_type_for_error_prompt() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut usage_limits = snapshot(/*percent*/ 100.0);
|
||||
usage_limits.rate_limit_reached_type =
|
||||
Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached);
|
||||
@@ -917,7 +867,6 @@ async fn header_rate_limit_snapshot_preserves_member_limit_type_for_error_prompt
|
||||
#[tokio::test]
|
||||
async fn usage_limit_error_remaps_stale_member_credits_state_to_usage_limit_prompt() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -954,7 +903,6 @@ async fn workspace_owner_limit_states_do_not_prompt_for_owner_nudge() {
|
||||
),
|
||||
] {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(limit_type);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -984,7 +932,6 @@ async fn workspace_owner_limit_states_render_state_specific_messages() {
|
||||
let mut rendered_cases = Vec::new();
|
||||
for (limit_type, error_kind, expected) in cases {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(limit_type);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -1007,7 +954,6 @@ async fn workspace_owner_limit_states_render_state_specific_messages() {
|
||||
#[tokio::test]
|
||||
async fn missing_rate_limit_reached_type_does_not_prompt_or_refresh() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
chat.on_rate_limit_snapshot(Some(snapshot(/*percent*/ 100.0)));
|
||||
|
||||
chat.on_rate_limit_error(
|
||||
@@ -1022,7 +968,6 @@ async fn missing_rate_limit_reached_type_does_not_prompt_or_refresh() {
|
||||
#[tokio::test]
|
||||
async fn workspace_owner_nudge_default_no_dismisses_without_sending() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -1039,7 +984,6 @@ async fn workspace_owner_nudge_default_no_dismisses_without_sending() {
|
||||
#[tokio::test]
|
||||
async fn workspace_owner_nudge_reappears_after_dismissing_no() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 100.0);
|
||||
limits.rate_limit_reached_type = Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
@@ -1082,7 +1026,6 @@ async fn workspace_owner_credits_nudge_completion_renders_feedback() {
|
||||
let mut rendered_cases = Vec::new();
|
||||
for (result, expected) in cases {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
chat.start_add_credits_nudge_email_request(AddCreditsNudgeCreditType::Credits);
|
||||
chat.finish_add_credits_nudge_email_request(result);
|
||||
let rendered = drain_insert_history(&mut rx)
|
||||
@@ -1119,7 +1062,6 @@ async fn workspace_owner_usage_limit_nudge_completion_renders_feedback() {
|
||||
let mut rendered_cases = Vec::new();
|
||||
for (result, expected) in cases {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
enable_workspace_owner_usage_nudge(&mut chat);
|
||||
chat.start_add_credits_nudge_email_request(AddCreditsNudgeCreditType::UsageLimit);
|
||||
chat.finish_add_credits_nudge_email_request(result);
|
||||
let rendered = drain_insert_history(&mut rx)
|
||||
|
||||
@@ -12,6 +12,10 @@ pub struct Cli {
|
||||
#[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
|
||||
#[arg(long = "strict-config", default_value_t = false)]
|
||||
pub strict_config: bool,
|
||||
|
||||
// Internal controls set by the top-level `codex resume` subcommand.
|
||||
// These are not exposed as user flags on the base `codex` command.
|
||||
#[clap(skip)]
|
||||
|
||||
+38
-3
@@ -8,7 +8,7 @@ use crate::legacy_core::config::Config;
|
||||
use crate::legacy_core::config::ConfigBuilder;
|
||||
use crate::legacy_core::config::ConfigOverrides;
|
||||
use crate::legacy_core::config::find_codex_home;
|
||||
use crate::legacy_core::config::load_config_as_toml_with_cli_and_loader_overrides;
|
||||
use crate::legacy_core::config::load_config_as_toml_with_cli_and_load_options;
|
||||
use crate::legacy_core::config::resolve_oss_provider;
|
||||
use crate::legacy_core::format_exec_policy_error_with_source;
|
||||
use crate::legacy_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
@@ -277,6 +277,7 @@ async fn start_embedded_app_server(
|
||||
config: Config,
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
feedback: codex_feedback::CodexFeedback,
|
||||
log_db: Option<log_db::LogDbLayer>,
|
||||
@@ -288,6 +289,7 @@ async fn start_embedded_app_server(
|
||||
config,
|
||||
cli_kv_overrides,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements,
|
||||
feedback,
|
||||
log_db,
|
||||
@@ -406,6 +408,7 @@ async fn start_app_server(
|
||||
config: Config,
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
feedback: codex_feedback::CodexFeedback,
|
||||
log_db: Option<log_db::LogDbLayer>,
|
||||
@@ -418,6 +421,7 @@ async fn start_app_server(
|
||||
config,
|
||||
cli_kv_overrides,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements,
|
||||
feedback,
|
||||
log_db,
|
||||
@@ -445,6 +449,7 @@ pub(crate) async fn start_app_server_for_picker(
|
||||
config.clone(),
|
||||
Vec::new(),
|
||||
LoaderOverrides::default(),
|
||||
/*strict_config*/ false,
|
||||
CloudRequirementsLoader::default(),
|
||||
codex_feedback::CodexFeedback::new(),
|
||||
/*log_db*/ None,
|
||||
@@ -475,6 +480,7 @@ async fn start_embedded_app_server_with<F, Fut>(
|
||||
config: Config,
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
feedback: codex_feedback::CodexFeedback,
|
||||
log_db: Option<log_db::LogDbLayer>,
|
||||
@@ -501,6 +507,7 @@ where
|
||||
config: Arc::new(config),
|
||||
cli_overrides: cli_kv_overrides,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements,
|
||||
feedback,
|
||||
log_db,
|
||||
@@ -716,6 +723,7 @@ pub async fn run_main(
|
||||
remote: Option<String>,
|
||||
remote_auth_token: Option<String>,
|
||||
) -> std::io::Result<AppExitInfo> {
|
||||
let strict_config = cli.strict_config;
|
||||
let remote_url = remote;
|
||||
if let (Some(websocket_url), Some(_)) = (remote_url.as_deref(), remote_auth_token.as_ref()) {
|
||||
validate_remote_auth_token_transport(websocket_url).map_err(std::io::Error::other)?;
|
||||
@@ -792,11 +800,14 @@ pub async fn run_main(
|
||||
config_cwd_for_app_server_target(cwd.as_deref(), &app_server_target, &environment_manager)?;
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
let config_toml = match load_config_as_toml_with_cli_and_loader_overrides(
|
||||
let config_toml = match load_config_as_toml_with_cli_and_load_options(
|
||||
&codex_home,
|
||||
config_cwd.as_ref(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
codex_config::ConfigLoadOptions {
|
||||
loader_overrides: loader_overrides.clone(),
|
||||
strict_config,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -891,6 +902,8 @@ pub async fn run_main(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -946,6 +959,8 @@ pub async fn run_main(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -1088,6 +1103,7 @@ pub async fn run_main(
|
||||
cli,
|
||||
arg0_paths,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
app_server_target,
|
||||
remote_cwd_override,
|
||||
config,
|
||||
@@ -1110,6 +1126,7 @@ async fn run_ratatui_app(
|
||||
cli: Cli,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
app_server_target: AppServerTarget,
|
||||
remote_cwd_override: Option<PathBuf>,
|
||||
initial_config: Config,
|
||||
@@ -1175,6 +1192,7 @@ async fn run_ratatui_app(
|
||||
initial_config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
log_db.clone(),
|
||||
@@ -1262,6 +1280,8 @@ async fn run_ratatui_app(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@@ -1464,6 +1484,8 @@ async fn run_ratatui_app(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
fallback_cwd,
|
||||
)
|
||||
.await
|
||||
@@ -1473,6 +1495,8 @@ async fn run_ratatui_app(
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
loader_overrides.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1514,6 +1538,7 @@ async fn run_ratatui_app(
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
log_db.clone(),
|
||||
@@ -1675,11 +1700,15 @@ async fn load_config_or_exit(
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
overrides: ConfigOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
) -> Config {
|
||||
load_config_or_exit_with_fallback_cwd(
|
||||
cli_kv_overrides,
|
||||
overrides,
|
||||
cloud_requirements,
|
||||
loader_overrides,
|
||||
strict_config,
|
||||
/*fallback_cwd*/ None,
|
||||
)
|
||||
.await
|
||||
@@ -1689,12 +1718,16 @@ async fn load_config_or_exit_with_fallback_cwd(
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
overrides: ConfigOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
loader_overrides: LoaderOverrides,
|
||||
strict_config: bool,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
) -> Config {
|
||||
#[allow(clippy::print_stderr)]
|
||||
match ConfigBuilder::default()
|
||||
.cli_overrides(cli_kv_overrides)
|
||||
.harness_overrides(overrides)
|
||||
.loader_overrides(loader_overrides)
|
||||
.strict_config(strict_config)
|
||||
.cloud_requirements(cloud_requirements)
|
||||
.fallback_cwd(fallback_cwd)
|
||||
.build()
|
||||
@@ -1766,6 +1799,7 @@ mod tests {
|
||||
config,
|
||||
Vec::new(),
|
||||
LoaderOverrides::default(),
|
||||
/*strict_config*/ false,
|
||||
CloudRequirementsLoader::default(),
|
||||
codex_feedback::CodexFeedback::new(),
|
||||
/*log_db*/ None,
|
||||
@@ -2288,6 +2322,7 @@ mod tests {
|
||||
config,
|
||||
Vec::new(),
|
||||
LoaderOverrides::default(),
|
||||
/*strict_config*/ false,
|
||||
CloudRequirementsLoader::default(),
|
||||
codex_feedback::CodexFeedback::new(),
|
||||
/*log_db*/ None,
|
||||
|
||||
@@ -1056,6 +1056,7 @@ mod tests {
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: Default::default(),
|
||||
strict_config: false,
|
||||
cloud_requirements: cloud_requirements_loader_for_storage(
|
||||
codex_home_path.clone(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
|
||||
@@ -37,6 +37,13 @@ pub struct CliConfigOverrides {
|
||||
}
|
||||
|
||||
impl CliConfigOverrides {
|
||||
/// Prepend root-level config flags so they have lower precedence than
|
||||
/// command-specific flags parsed after a subcommand.
|
||||
pub fn prepend_root_overrides(&mut self, root_overrides: Self) {
|
||||
self.raw_overrides
|
||||
.splice(0..0, root_overrides.raw_overrides);
|
||||
}
|
||||
|
||||
/// Parse the raw strings captured from the CLI into a list of `(path,
|
||||
/// value)` tuples where `value` is a `serde_json::Value`.
|
||||
pub fn parse_overrides(&self) -> Result<Vec<(String, Value)>, String> {
|
||||
@@ -190,6 +197,24 @@ mod tests {
|
||||
assert_eq!(parsed[0].1.as_bool(), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepends_root_overrides() {
|
||||
let mut subcommand_overrides = CliConfigOverrides {
|
||||
raw_overrides: vec!["model=\"gpt-5.2\"".to_string()],
|
||||
};
|
||||
subcommand_overrides.prepend_root_overrides(CliConfigOverrides {
|
||||
raw_overrides: vec!["model=\"gpt-5.1\"".to_string()],
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
subcommand_overrides.raw_overrides,
|
||||
vec![
|
||||
"model=\"gpt-5.1\"".to_string(),
|
||||
"model=\"gpt-5.2\"".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_inline_table() {
|
||||
let v = parse_toml_value("{a = 1, b = 2}").expect("parse");
|
||||
|
||||
Generated
+116
-110
@@ -2,6 +2,12 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2026-05-05T22:35:45Z"
|
||||
|
||||
[options.exclude-newer-package]
|
||||
openai-codex-cli-bin = "2026-05-10T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
@@ -350,7 +356,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.4"
|
||||
version = "2.13.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
@@ -358,125 +364,125 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.4"
|
||||
version = "2.46.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/98/b50eb9a411e87483b5c65dba4fa430a06bac4234d3403a40e5a9905ebcd0/pydantic_core-2.46.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da3786b8018e60349680720158cc19161cc3b4bdd815beb0a321cd5ce1ad5b1", size = 2108971, upload-time = "2026-04-20T14:43:51.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/4b/f364b9d161718ff2217160a4b5d41ce38de60aed91c3689ebffa1c939d23/pydantic_core-2.46.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc0988cb29d21bf4a9d5cf2ef970b5c0e38d8d8e107a493278c05dc6c1dda69f", size = 1949588, upload-time = "2026-04-20T14:44:10.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/8b/30bd03ee83b2f5e29f5ba8e647ab3c456bf56f2ec72fdbcc0215484a0854/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f9067c3bfadd04c55484b89c0d267981b2f3512850f6f66e1e74204a4e4ce3", size = 1975986, upload-time = "2026-04-20T14:43:57.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/54/13ccf954d84ec275d5d023d5786e4aa48840bc9f161f2838dc98e1153518/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a642ac886ecf6402d9882d10c405dcf4b902abeb2972cd5fb4a48c83cd59279a", size = 2055830, upload-time = "2026-04-20T14:44:15.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/0e/65f38125e660fdbd72aa858e7dfae893645cfa0e7b13d333e174a367cd23/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79f561438481f28681584b89e2effb22855e2179880314bcddbf5968e935e807", size = 2222340, upload-time = "2026-04-20T14:41:51.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/88/f3ab7739efe0e7e80777dbb84c59eb98518e3f57ea433206194c2e425272/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57a973eae4665352a47cf1a99b4ee864620f2fe663a217d7a8da68a1f3a5bfda", size = 2280727, upload-time = "2026-04-20T14:41:30.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/6d/c228219080817bec4982f9531cadb18da6aaa770fdeb114f49c237ac2c9f/pydantic_core-2.46.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83d002b97072a53ea150d63e0a3adfae5670cef5aa8a6e490240e482d3b22e57", size = 2092158, upload-time = "2026-04-20T14:44:07.305Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/b1/525a16711e7c6d61635fac3b0bd54600b5c5d9f60c6fc5aaab26b64a2297/pydantic_core-2.46.3-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b40ddd51e7c44b28cfaef746c9d3c506d658885e0a46f9eeef2ee815cbf8e045", size = 2116626, upload-time = "2026-04-20T14:42:34.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/7c/17d30673351439a6951bf54f564cf2443ab00ae264ec9df00e2efd710eb5/pydantic_core-2.46.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac5ec7fb9b87f04ee839af2d53bcadea57ded7d229719f56c0ed895bff987943", size = 2160691, upload-time = "2026-04-20T14:41:14.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/66/af8adbcbc0886ead7f1a116606a534d75a307e71e6e08226000d51b880d2/pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3b11c812f61b3129c4905781a2601dfdfdea5fe1e6c1cfb696b55d14e9c054f", size = 2182543, upload-time = "2026-04-20T14:40:48.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/37/6de71e0f54c54a4190010f57deb749e1ddf75c568ada3b1320b70067f121/pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1108da631e602e5b3c38d6d04fe5bb3bfa54349e6918e3ca6cf570b2e2b2f9d4", size = 2324513, upload-time = "2026-04-20T14:42:36.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b1/9fc74ce94f603d5ef59ff258ca9c2c8fb902fb548d340a96f77f4d1c3b7f/pydantic_core-2.46.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:de885175515bcfa98ae618c1df7a072f13d179f81376c8007112af20567fd08a", size = 2361853, upload-time = "2026-04-20T14:43:24.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/d0/4c652fc592db35f100279ee751d5a145aca1b9a7984b9684ba7c1b5b0535/pydantic_core-2.46.3-cp310-cp310-win32.whl", hash = "sha256:d11058e3201527d41bc6b545c79187c9e4bf85e15a236a6007f0e991518882b7", size = 1980465, upload-time = "2026-04-20T14:44:46.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/b8/a920453c38afbe1f355e1ea0b0d94a0a3e0b0879d32d793108755fa171d5/pydantic_core-2.46.3-cp310-cp310-win_amd64.whl", hash = "sha256:3612edf65c8ea67ac13616c4d23af12faef1ae435a8a93e5934c2a0cbbdd1fd6", size = 2073884, upload-time = "2026-04-20T14:43:01.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/a2/1ba90a83e85a3f94c796b184f3efde9c72f2830dcda493eea8d59ba78e6d/pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5", size = 2106740, upload-time = "2026-04-20T14:41:20.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/f6/99ae893c89a0b9d3daec9f95487aa676709aa83f67643b3f0abaf4ab628a/pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c", size = 1948293, upload-time = "2026-04-20T14:43:42.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/b8/2e8e636dc9e3f16c2e16bf0849e24be82c5ee82c603c65fc0326666328fc/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e", size = 1973222, upload-time = "2026-04-20T14:41:57.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/36/0e730beec4d83c5306f417afbd82ff237d9a21e83c5edf675f31ed84c1fe/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287", size = 2053852, upload-time = "2026-04-20T14:40:43.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/f0/3071131f47e39136a17814576e0fada9168569f7f8c0e6ac4d1ede6a4958/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe", size = 2221134, upload-time = "2026-04-20T14:43:03.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/a9/a2dc023eec5aa4b02a467874bad32e2446957d2adcab14e107eab502e978/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050", size = 2279785, upload-time = "2026-04-20T14:41:19.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/44/93f489d16fb63fbd41c670441536541f6e8cfa1e5a69f40bc9c5d30d8c90/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2", size = 2089404, upload-time = "2026-04-20T14:43:10.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/78/8692e3aa72b2d004f7a5d937f1dfdc8552ba26caf0bec75f342c40f00dec/pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa", size = 2114898, upload-time = "2026-04-20T14:44:51.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/62/e83133f2e7832532060175cebf1f13748f4c7e7e7165cdd1f611f174494b/pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c", size = 2157856, upload-time = "2026-04-20T14:43:46.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/ec/6a500e3ad7718ee50583fae79c8651f5d37e3abce1fa9ae177ae65842c53/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf", size = 2180168, upload-time = "2026-04-20T14:42:00.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/53/8267811054b1aa7fc1dc7ded93812372ef79a839f5e23558136a6afbfde1/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b", size = 2322885, upload-time = "2026-04-20T14:41:05.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c1/1c0acdb3aa0856ddc4ecc55214578f896f2de16f400cf51627eb3c26c1c4/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e", size = 2360328, upload-time = "2026-04-20T14:41:43.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/d0/ef39cd0f4a926814f360e71c1adeab48ad214d9727e4deb48eedfb5bce1a/pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb", size = 1979464, upload-time = "2026-04-20T14:43:12.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/9c/f41951b0d858e343f1cf09398b2a7b3014013799744f2c4a8ad6a3eec4f2/pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346", size = 2070837, upload-time = "2026-04-20T14:41:47.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/1e/264a17cd582f6ed50950d4d03dd5fefd84e570e238afe1cb3e25cf238769/pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6", size = 2053647, upload-time = "2026-04-20T14:42:27.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/7f/03dbad45cd3aa9083fbc93c210ae8b005af67e4136a14186950a747c6874/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:9715525891ed524a0a1eb6d053c74d4d4ad5017677fb00af0b7c2644a31bae46", size = 2105683, upload-time = "2026-04-20T14:42:19.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/22/4dc186ac8ea6b257e9855031f51b62a9637beac4d68ac06bee02f046f836/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:9d2f400712a99a013aff420ef1eb9be077f8189a36c1e3ef87660b4e1088a874", size = 1940052, upload-time = "2026-04-20T14:43:59.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/ca/d376391a5aff1f2e8188960d7873543608130a870961c2b6b5236627c116/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd2aab0e2e9dc2daf36bd2686c982535d5e7b1d930a1344a7bb6e82baab42a76", size = 1988172, upload-time = "2026-04-20T14:41:17.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/6b/523b9f85c23788755d6ab949329de692a2e3a584bc6beb67fef5e035aa9d/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e9d76736da5f362fabfeea6a69b13b7f2be405c6d6966f06b2f6bfff7e64531", size = 2128596, upload-time = "2026-04-20T14:40:41.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/da/99d40830684f81dec901cac521b5b91c095394cc1084b9433393cde1c2df/pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25", size = 2107973, upload-time = "2026-04-20T14:42:06.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/a5/87024121818d75bbb2a98ddbaf638e40e7a18b5e0f5492c9ca4b1b316107/pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3", size = 1947191, upload-time = "2026-04-20T14:43:14.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/62/0c1acfe10945b83a6a59d19fbaa92f48825381509e5701b855c08f13db76/pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536", size = 2123791, upload-time = "2026-04-20T14:43:22.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/3e/3b2393b4c8f44285561dc30b00cf307a56a2eff7c483a824db3b8221ca51/pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1", size = 2153197, upload-time = "2026-04-20T14:44:27.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/75/5af02fb35505051eee727c061f2881c555ab4f8ddb2d42da715a42c9731b/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c", size = 2181073, upload-time = "2026-04-20T14:43:20.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/92/7e0e1bd9ca3c68305db037560ca2876f89b2647deb2f8b6319005de37505/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85", size = 2315886, upload-time = "2026-04-20T14:44:04.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/d8/101655f27eaf3e44558ead736b2795d12500598beed4683f279396fa186e/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8", size = 2360528, upload-time = "2026-04-20T14:40:47.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/0f/1c34a74c8d07136f0d729ffe5e1fdab04fbdaa7684f61a92f92511a84a15/pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff", size = 2184144, upload-time = "2026-04-20T14:42:57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user