mirror of
https://github.com/openai/codex.git
synced 2026-04-30 01:16:54 +00:00
Add session config loader interface (#18208)
## Why Cloud-hosted sessions need a way for the service that starts or manages a thread to provide session-owned config without treating all config as if it came from the same user/project/workspace TOML stack. The important boundary is ownership: some values should be controlled by the session/orchestrator, some by the authenticated user, and later some may come from the executor. The earlier broad config-store shape made that boundary too fuzzy and overlapped heavily with the existing filesystem-backed config loader. This PR starts with the smaller piece we need now: a typed session config loader that can feed the existing config layer stack while preserving the normal precedence and merge behavior. ## What Changed - Added `ThreadConfigLoader` and related typed payloads in `codex-config`. - `SessionThreadConfig` currently supports `model_provider`, `model_providers`, and feature flags. - `UserThreadConfig` is present as an ownership boundary, but does not yet add TOML-backed fields. - `NoopThreadConfigLoader` preserves existing behavior when no external loader is configured. - `StaticThreadConfigLoader` supports tests and simple callers. - Taught thread config sources to produce ordinary `ConfigLayerEntry` values so the existing `ConfigLayerStack` remains the place where precedence and merging happen. - Wired the loader through `ConfigBuilder`, the config loader, and app-server startup paths so app-server can provide session-owned config before deriving a thread config. - Added coverage for: - translating typed thread config into config layers, - inserting thread config layers into the stack at the right precedence, - applying session-provided model provider and feature settings when app-server derives config from thread params. ## Follow-Ups This intentionally stops short of adding the remote/service transport. The next pieces are expected to be: 1. Define the proto/API shape for this interface. 2. Add a client implementation that can source session config from the service side. ## Verification - Added unit coverage in `codex-config` for the loader and layer conversion. - Added `codex-core` config loader coverage for thread config layer precedence. - Added app-server coverage that verifies session thread config wins over request-provided config for model provider and feature settings.
This commit is contained in:
@@ -213,6 +213,7 @@ use codex_backend_client::AddCreditsNudgeCreditType as BackendAddCreditsNudgeCre
|
||||
use codex_backend_client::Client as BackendClient;
|
||||
use codex_chatgpt::connectors;
|
||||
use codex_cloud_requirements::cloud_requirements_loader;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::ForkSnapshot;
|
||||
@@ -479,6 +480,7 @@ pub(crate) struct CodexMessageProcessor {
|
||||
cli_overrides: Arc<RwLock<Vec<(String, TomlValue)>>>,
|
||||
runtime_feature_enablement: Arc<RwLock<BTreeMap<String, bool>>>,
|
||||
cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
|
||||
thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
active_login: Arc<Mutex<Option<ActiveLogin>>>,
|
||||
pending_thread_unloads: Arc<Mutex<HashSet<ThreadId>>>,
|
||||
thread_state_manager: ThreadStateManager,
|
||||
@@ -639,6 +641,7 @@ pub(crate) struct CodexMessageProcessorArgs {
|
||||
pub(crate) cli_overrides: Arc<RwLock<Vec<(String, TomlValue)>>>,
|
||||
pub(crate) runtime_feature_enablement: Arc<RwLock<BTreeMap<String, bool>>>,
|
||||
pub(crate) cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
|
||||
pub(crate) thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
pub(crate) feedback: CodexFeedback,
|
||||
pub(crate) log_db: Option<LogDbLayer>,
|
||||
}
|
||||
@@ -726,6 +729,7 @@ impl CodexMessageProcessor {
|
||||
cli_overrides,
|
||||
runtime_feature_enablement,
|
||||
cloud_requirements,
|
||||
thread_config_loader,
|
||||
feedback,
|
||||
log_db,
|
||||
} = args;
|
||||
@@ -740,6 +744,7 @@ impl CodexMessageProcessor {
|
||||
cli_overrides,
|
||||
runtime_feature_enablement,
|
||||
cloud_requirements,
|
||||
thread_config_loader,
|
||||
active_login: Arc::new(Mutex::new(None)),
|
||||
pending_thread_unloads: Arc::new(Mutex::new(HashSet::new())),
|
||||
thread_state_manager: ThreadStateManager::new(),
|
||||
@@ -2433,12 +2438,14 @@ impl CodexMessageProcessor {
|
||||
};
|
||||
let request_trace = request_context.request_trace();
|
||||
let runtime_feature_enablement = self.current_runtime_feature_enablement();
|
||||
let thread_config_loader = Arc::clone(&self.thread_config_loader);
|
||||
let thread_start_task = async move {
|
||||
Self::thread_start_task(
|
||||
listener_task_context,
|
||||
cli_overrides,
|
||||
runtime_feature_enablement,
|
||||
cloud_requirements,
|
||||
thread_config_loader,
|
||||
request_id,
|
||||
app_server_client_name,
|
||||
app_server_client_version,
|
||||
@@ -2515,6 +2522,7 @@ impl CodexMessageProcessor {
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
runtime_feature_enablement: BTreeMap<String, bool>,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
request_id: ConnectionRequestId,
|
||||
app_server_client_name: Option<String>,
|
||||
app_server_client_version: Option<String>,
|
||||
@@ -2532,6 +2540,7 @@ impl CodexMessageProcessor {
|
||||
&cli_overrides,
|
||||
config_overrides.clone(),
|
||||
typesafe_overrides.clone(),
|
||||
Arc::clone(&thread_config_loader),
|
||||
&cloud_requirements,
|
||||
&listener_task_context.codex_home,
|
||||
&runtime_feature_enablement,
|
||||
@@ -2613,6 +2622,7 @@ impl CodexMessageProcessor {
|
||||
cli_overrides_for_reload,
|
||||
config_overrides,
|
||||
typesafe_overrides,
|
||||
thread_config_loader,
|
||||
&cloud_requirements,
|
||||
&listener_task_context.codex_home,
|
||||
&runtime_feature_enablement,
|
||||
@@ -6528,6 +6538,7 @@ impl CodexMessageProcessor {
|
||||
&cli_overrides,
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
self.thread_config_loader.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -9452,6 +9463,7 @@ async fn derive_config_from_params(
|
||||
cli_overrides: &[(String, TomlValue)],
|
||||
request_overrides: Option<HashMap<String, serde_json::Value>>,
|
||||
typesafe_overrides: ConfigOverrides,
|
||||
thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
cloud_requirements: &CloudRequirementsLoader,
|
||||
codex_home: &Path,
|
||||
runtime_feature_enablement: &BTreeMap<String, bool>,
|
||||
@@ -9472,6 +9484,7 @@ async fn derive_config_from_params(
|
||||
.cli_overrides(merged_cli_overrides)
|
||||
.harness_overrides(typesafe_overrides)
|
||||
.cloud_requirements(cloud_requirements.clone())
|
||||
.thread_config_loader(thread_config_loader)
|
||||
.build()
|
||||
.await?;
|
||||
apply_runtime_feature_enablement(&mut config, runtime_feature_enablement);
|
||||
@@ -10402,6 +10415,11 @@ mod tests {
|
||||
use chrono::Utc;
|
||||
use codex_app_server_protocol::ServerRequestPayload;
|
||||
use codex_app_server_protocol::ToolRequestUserInputParams;
|
||||
use codex_config::SessionThreadConfig;
|
||||
use codex_config::StaticThreadConfigLoader;
|
||||
use codex_config::ThreadConfigSource;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -10414,6 +10432,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
@@ -10566,6 +10585,64 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn derive_config_from_params_uses_session_thread_config_model_provider() -> Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let session_provider = ModelProviderInfo {
|
||||
name: "session".to_string(),
|
||||
base_url: Some("http://127.0.0.1:8061/api/codex".to_string()),
|
||||
env_key: None,
|
||||
env_key_instructions: None,
|
||||
experimental_bearer_token: None,
|
||||
auth: None,
|
||||
wire_api: WireApi::Responses,
|
||||
query_params: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
request_max_retries: None,
|
||||
stream_max_retries: None,
|
||||
stream_idle_timeout_ms: None,
|
||||
websocket_connect_timeout_ms: None,
|
||||
requires_openai_auth: false,
|
||||
supports_websockets: true,
|
||||
};
|
||||
let config = derive_config_from_params(
|
||||
&[],
|
||||
Some(HashMap::from([
|
||||
("model_provider".to_string(), json!("request")),
|
||||
("features.plugins".to_string(), json!(true)),
|
||||
(
|
||||
"model_providers.session".to_string(),
|
||||
json!({
|
||||
"name": "request",
|
||||
"base_url": "http://127.0.0.1:9999/api/codex",
|
||||
"wire_api": "responses",
|
||||
}),
|
||||
),
|
||||
])),
|
||||
ConfigOverrides::default(),
|
||||
Arc::new(StaticThreadConfigLoader::new(vec![
|
||||
ThreadConfigSource::Session(SessionThreadConfig {
|
||||
model_provider: Some("session".to_string()),
|
||||
model_providers: HashMap::from([(
|
||||
"session".to_string(),
|
||||
session_provider.clone(),
|
||||
)]),
|
||||
features: BTreeMap::from([("plugins".to_string(), false)]),
|
||||
}),
|
||||
])),
|
||||
&CloudRequirementsLoader::default(),
|
||||
temp_dir.path(),
|
||||
&BTreeMap::new(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.model_provider_id, "session");
|
||||
assert_eq!(config.model_provider, session_provider);
|
||||
assert!(!config.features.enabled(Feature::Plugins));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_resume_override_mismatches_includes_service_tier() {
|
||||
let request = ThreadResumeParams {
|
||||
|
||||
@@ -75,6 +75,7 @@ use codex_app_server_protocol::Result;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
@@ -116,6 +117,8 @@ pub struct InProcessStartArgs {
|
||||
pub loader_overrides: LoaderOverrides,
|
||||
/// Preloaded cloud requirements provider.
|
||||
pub cloud_requirements: CloudRequirementsLoader,
|
||||
/// Loader used to fetch typed thread config sources before a thread starts.
|
||||
pub thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
/// Feedback sink used by app-server/core telemetry and logs.
|
||||
pub feedback: CodexFeedback,
|
||||
/// SQLite tracing layer used to flush recently emitted logs before feedback upload.
|
||||
@@ -397,6 +400,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
cli_overrides: args.cli_overrides,
|
||||
loader_overrides: args.loader_overrides,
|
||||
cloud_requirements: args.cloud_requirements,
|
||||
thread_config_loader: args.thread_config_loader,
|
||||
feedback: args.feedback,
|
||||
log_db: args.log_db,
|
||||
config_warnings: args.config_warnings,
|
||||
@@ -731,6 +735,7 @@ mod tests {
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
environment_manager: Arc::new(EnvironmentManager::new(/*exec_server_url*/ None)),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_cloud_requirements::cloud_requirements_loader;
|
||||
use codex_config::NoopThreadConfigLoader;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
@@ -416,6 +417,7 @@ pub async fn run_main_with_transport(
|
||||
}
|
||||
};
|
||||
let loader_overrides_for_config_api = loader_overrides.clone();
|
||||
let thread_config_loader = Arc::new(NoopThreadConfigLoader);
|
||||
let mut config_warnings = Vec::new();
|
||||
let config = match ConfigBuilder::default()
|
||||
.cli_overrides(cli_kv_overrides.clone())
|
||||
@@ -659,6 +661,7 @@ pub async fn run_main_with_transport(
|
||||
cli_overrides,
|
||||
loader_overrides,
|
||||
cloud_requirements: cloud_requirements.clone(),
|
||||
thread_config_loader,
|
||||
feedback: feedback.clone(),
|
||||
log_db,
|
||||
config_warnings,
|
||||
|
||||
@@ -63,6 +63,7 @@ use codex_app_server_protocol::ServerRequestPayload;
|
||||
use codex_app_server_protocol::experimental_required_message;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_chatgpt::connectors;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
@@ -235,6 +236,7 @@ pub(crate) struct MessageProcessorArgs {
|
||||
pub(crate) cli_overrides: Vec<(String, TomlValue)>,
|
||||
pub(crate) loader_overrides: LoaderOverrides,
|
||||
pub(crate) cloud_requirements: CloudRequirementsLoader,
|
||||
pub(crate) thread_config_loader: Arc<dyn ThreadConfigLoader>,
|
||||
pub(crate) feedback: CodexFeedback,
|
||||
pub(crate) log_db: Option<LogDbLayer>,
|
||||
pub(crate) config_warnings: Vec<ConfigWarningNotification>,
|
||||
@@ -256,6 +258,7 @@ impl MessageProcessor {
|
||||
cli_overrides,
|
||||
loader_overrides,
|
||||
cloud_requirements,
|
||||
thread_config_loader,
|
||||
feedback,
|
||||
log_db,
|
||||
config_warnings,
|
||||
@@ -301,6 +304,7 @@ impl MessageProcessor {
|
||||
cli_overrides: cli_overrides.clone(),
|
||||
runtime_feature_enablement: runtime_feature_enablement.clone(),
|
||||
cloud_requirements: cloud_requirements.clone(),
|
||||
thread_config_loader,
|
||||
feedback,
|
||||
log_db,
|
||||
});
|
||||
|
||||
@@ -245,6 +245,7 @@ fn build_test_processor(
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
config_warnings: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user