mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Wire shared runtime authentication through threads and API clients (#59177)
## What changed - Share a lazily registered runtime across threads, model discovery, and extension clients when `runtime_agent_instances` is enabled, while honoring each thread's authentication mode and credential owner. - Bind model catalogs to the effective provider and authentication context while preserving cache policy and live API-key discovery settings. - Resolve credentials, routing, and transport policy together for each request. Preserve proxy and TLS settings, validate runtime account selectors, and prevent pooled connections from crossing runtime identities. - Accept supported ChatGPT subdomains when selecting the registration environment. ## Testing Add integration coverage for shared runtime registration, dual-header and assertion-only modes, disabled and third-party providers, credential replacement, subagents, and delegated review. Add coverage for catalog cache policy and runtime-aware connection reuse. GitOrigin-RevId: 99dd9d987c29c93597a648f6586f98ac6a932c3c
This commit is contained in:
@@ -113,8 +113,12 @@ async fn publication_harness(
|
||||
});
|
||||
harness.manager = ThreadManager::new(
|
||||
&harness.config,
|
||||
harness.manager.auth_manager(),
|
||||
harness.manager.get_models_manager(),
|
||||
crate::build_startup_models(
|
||||
&harness.config,
|
||||
harness.manager.auth_manager(),
|
||||
"spawn-publication-cleanup-test".to_string(),
|
||||
crate::test_support::fixed_model_catalog(harness.manager.get_models_manager()),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
harness.manager.environment_manager(),
|
||||
@@ -124,7 +128,6 @@ async fn publication_harness(
|
||||
harness.manager.image_store(),
|
||||
crate::ThreadStoreBuilder::from_config(&harness.config, harness.state_db.clone()).build(),
|
||||
Some(graph.clone()),
|
||||
"spawn-publication-cleanup-test".to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ use codex_history::ResumedHistory;
|
||||
use codex_history::RolloutItem;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_models_manager::model_info::model_info_from_slug;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ResponseItemId;
|
||||
@@ -3785,8 +3786,14 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
|
||||
harness.manager = ThreadManager::new(
|
||||
&parent_config,
|
||||
auth_manager.clone(),
|
||||
crate::thread_manager::build_models_manager(&parent_config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&parent_config,
|
||||
auth_manager.clone(),
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(
|
||||
parent_config.codex_home.to_path_buf(),
|
||||
)),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -3797,7 +3804,6 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
|
||||
crate::ThreadStoreBuilder::from_config(&parent_config, harness.state_db.clone())
|
||||
.build(),
|
||||
crate::thread_manager::local_agent_graph_store_from_state_db(harness.state_db.as_ref()),
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -4941,8 +4947,12 @@ async fn resume_thread_subagent_restores_stored_metadata() {
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
crate::thread_manager::build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -4952,7 +4962,6 @@ async fn resume_thread_subagent_restores_stored_metadata() {
|
||||
crate::thread_manager::passthrough_image_store(),
|
||||
thread_store.clone(),
|
||||
/*agent_graph_store*/ None,
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
|
||||
+17
-166
@@ -54,7 +54,6 @@ use codex_api::RealtimeSessionConfig as ApiRealtimeSessionConfig;
|
||||
use codex_api::Reasoning;
|
||||
use codex_api::ReasoningContext;
|
||||
use codex_api::RequestTelemetry;
|
||||
use codex_api::ReqwestTransport;
|
||||
use codex_api::ResponseCreateWsRequest;
|
||||
use codex_api::ResponsesApiRequest;
|
||||
use codex_api::ResponsesClient as ApiResponsesClient;
|
||||
@@ -71,22 +70,17 @@ use codex_api::auth_header_telemetry;
|
||||
use codex_api::build_session_headers;
|
||||
use codex_api::create_text_param_for_request;
|
||||
use codex_api::response_create_client_metadata;
|
||||
use codex_features::RuntimeAuthMode;
|
||||
use codex_http_client::ClientRouteClass;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::RefreshTokenError;
|
||||
use codex_login::UnauthorizedRecovery;
|
||||
use codex_login::default_client::ClientRedirectPolicy;
|
||||
use codex_login::default_client::add_originator_header;
|
||||
use codex_login::default_client::create_client_for_route;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::WEBSOCKET_CONTINUATION_COUNT_METRIC;
|
||||
use codex_otel::current_span_w3c_trace_context;
|
||||
use codex_protocol::ResponseItemId;
|
||||
use codex_protocol::auth::AuthMode;
|
||||
use codex_runtime_agent_instance::RuntimeAuthManager;
|
||||
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
@@ -147,14 +141,13 @@ use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use codex_model_provider::AgentIdentitySessionFallback;
|
||||
use codex_model_provider::ProviderAuthScope;
|
||||
use codex_model_provider::ProviderUnauthorizedRecovery;
|
||||
use codex_model_provider::ResolvedProviderAuth;
|
||||
use codex_model_provider::ResolvedProviderRequest as CurrentClientSetup;
|
||||
use codex_model_provider::ResponsesConnectionKey;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::WorkspaceRoutingContext;
|
||||
use codex_model_provider::create_model_provider;
|
||||
#[cfg(test)]
|
||||
use codex_model_provider_info::DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::CodexErrorDetails;
|
||||
@@ -235,44 +228,6 @@ enum ClientRouting {
|
||||
ConfiguredProvider,
|
||||
}
|
||||
|
||||
/// Resolved API client setup for a single request attempt.
|
||||
///
|
||||
/// Keeping this as a single bundle ensures prewarm and normal request paths
|
||||
/// share the same auth/provider setup flow.
|
||||
struct CurrentClientSetup {
|
||||
auth: Option<CodexAuth>,
|
||||
auth_owner_generation: Option<u64>,
|
||||
auth_revision: Option<u64>,
|
||||
api_provider: ApiProvider,
|
||||
http_client_factory: HttpClientFactory,
|
||||
redirect_policy: ClientRedirectPolicy,
|
||||
api_auth: SharedAuthProvider,
|
||||
agent_identity_telemetry: Option<AgentIdentityTelemetry>,
|
||||
runtime_id: Option<String>,
|
||||
}
|
||||
|
||||
impl CurrentClientSetup {
|
||||
fn build_api_transport(&self, endpoint: &str) -> Result<ReqwestTransport> {
|
||||
let api_provider = &self.api_provider;
|
||||
let redirect_policy = if api_provider
|
||||
.headers
|
||||
.contains_key(codex_model_provider::ACCOUNT_ROUTING_HEADER)
|
||||
{
|
||||
ClientRedirectPolicy::Reject
|
||||
} else {
|
||||
self.redirect_policy
|
||||
};
|
||||
let client = create_client_for_route(
|
||||
&self.http_client_factory,
|
||||
&api_provider.url_for_path(endpoint),
|
||||
ClientRouteClass::Api,
|
||||
redirect_policy,
|
||||
)
|
||||
.map_err(std::io::Error::from)?;
|
||||
Ok(ReqwestTransport::from_http_client(client))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct RequestRouteTelemetry {
|
||||
endpoint: &'static str,
|
||||
@@ -309,7 +264,6 @@ pub struct ModelClient {
|
||||
executed_tool_calls: Option<ExecutedToolCalls>,
|
||||
// Resolved once when the session is created, like other session feature flags.
|
||||
api_key_cyber_access_programs: cyber_access_program::ApiKeyCyberAccessPrograms,
|
||||
runtime_auth: Option<(Arc<RuntimeAuthManager>, RuntimeAuthMode)>,
|
||||
}
|
||||
|
||||
/// A turn-scoped streaming session created from a [`ModelClient`].
|
||||
@@ -581,10 +535,9 @@ impl ModelClient {
|
||||
/// HTTP client factory must come from the effective session configuration so every transport
|
||||
/// observes the resolved outbound proxy policy.
|
||||
pub fn new(
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
model_provider: SharedModelProvider,
|
||||
agent_identity_policy: AgentIdentityAuthPolicy,
|
||||
thread_id: ThreadId,
|
||||
provider_info: ModelProviderInfo,
|
||||
session_source: SessionSource,
|
||||
originator: String,
|
||||
model_verbosity: Option<VerbosityConfig>,
|
||||
@@ -600,7 +553,6 @@ impl ModelClient {
|
||||
workspace_routing: WorkspaceRoutingContext,
|
||||
request_contributors: Vec<Arc<dyn codex_extension_api::ModelRequestContributor>>,
|
||||
) -> Self {
|
||||
let model_provider = create_model_provider(provider_info, auth_manager);
|
||||
let codex_api_key_env_enabled = model_provider
|
||||
.auth_manager()
|
||||
.as_ref()
|
||||
@@ -647,7 +599,6 @@ impl ModelClient {
|
||||
codex_responses_headers: None,
|
||||
event_sender: None,
|
||||
http_client_factory,
|
||||
runtime_auth: None,
|
||||
restored_history: false,
|
||||
request_contributors,
|
||||
executed_tool_calls: None,
|
||||
@@ -672,24 +623,6 @@ impl ModelClient {
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the harness-owned runtime for this first-party provider. The owner must share this
|
||||
/// client's credential manager. Provider-specific overrides need their own runtime owner.
|
||||
pub fn with_runtime_auth(
|
||||
mut self,
|
||||
manager: Arc<RuntimeAuthManager>,
|
||||
mode: RuntimeAuthMode,
|
||||
) -> Result<Self> {
|
||||
let info = self.state.provider.info();
|
||||
codex_model_provider::validate_runtime_destination(info.base_url.as_deref())?;
|
||||
codex_model_provider::validate_runtime_provider(
|
||||
info,
|
||||
self.auth_manager().as_ref(),
|
||||
&manager,
|
||||
)?;
|
||||
self.runtime_auth = Some((manager, mode));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub(crate) fn with_session_context(
|
||||
mut self,
|
||||
prompt_cache_key: PromptCacheKey,
|
||||
@@ -834,7 +767,7 @@ impl ModelClient {
|
||||
if let Some(api_provider) = api_provider_override {
|
||||
client_setup.api_provider = api_provider;
|
||||
}
|
||||
let transport = client_setup.build_api_transport(REALTIME_CALLS_ENDPOINT)?;
|
||||
let transport = client_setup.http_transport(REALTIME_CALLS_ENDPOINT)?;
|
||||
let response =
|
||||
ApiRealtimeCallClient::new(transport, client_setup.api_provider, client_setup.api_auth)
|
||||
.create_with_session_and_headers(sdp, session_config, extra_headers)
|
||||
@@ -882,7 +815,7 @@ impl ModelClient {
|
||||
let client_setup = self
|
||||
.current_client_setup(ClientRouting::ConfiguredProvider)
|
||||
.await?;
|
||||
let transport = client_setup.build_api_transport(MEMORIES_SUMMARIZE_ENDPOINT)?;
|
||||
let transport = client_setup.http_transport(MEMORIES_SUMMARIZE_ENDPOINT)?;
|
||||
let request_telemetry = Self::build_request_telemetry(
|
||||
session_telemetry,
|
||||
AuthRequestTelemetryContext::new(
|
||||
@@ -1206,61 +1139,6 @@ impl ModelClient {
|
||||
/// This centralizes setup used by both prewarm and normal request paths so they stay in
|
||||
/// lockstep when auth/provider resolution changes.
|
||||
async fn current_client_setup(&self, routing: ClientRouting) -> Result<CurrentClientSetup> {
|
||||
if let Some((manager, mode)) = &self.runtime_auth {
|
||||
let selected = codex_model_provider::select_runtime_auth(
|
||||
manager,
|
||||
&self.state.provider.info().build_header_map()?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| self.state.provider.map_api_error(error.into()))?;
|
||||
let auth = selected.auth().clone();
|
||||
let auth_revision = Some(selected.change_state().generation);
|
||||
let auth_owner_generation = Some(selected.change_state().owner_generation);
|
||||
let api_provider = self.state.provider.api_provider_for_auth(Some(&auth))?;
|
||||
let (api_provider, redirect_policy) = match routing {
|
||||
ClientRouting::Workspace => {
|
||||
let resolved = self
|
||||
.state
|
||||
.provider
|
||||
.route_responses_api_provider(
|
||||
api_provider,
|
||||
Some(&auth),
|
||||
&self.state.workspace_routing,
|
||||
)
|
||||
.await?;
|
||||
(resolved.provider, resolved.redirect_policy)
|
||||
}
|
||||
ClientRouting::ConfiguredProvider => (api_provider, ClientRedirectPolicy::Default),
|
||||
};
|
||||
let runtime = selected.runtime();
|
||||
codex_model_provider::validate_runtime_selectors(
|
||||
&api_provider.headers,
|
||||
runtime.actor().info(),
|
||||
)?;
|
||||
let runtime_id = Some(runtime.id().to_string());
|
||||
let http_client_factory = selected.http_client_factory().clone();
|
||||
let resolved_auth = self
|
||||
.state
|
||||
.provider
|
||||
.compose_auth(ResolvedProviderAuth {
|
||||
auth: selected.clone().into_auth_provider(*mode),
|
||||
agent_identity_telemetry: None,
|
||||
runtime: Some(selected),
|
||||
})
|
||||
.await?;
|
||||
tracing::debug!(runtime_auth_mode = ?mode, "selected runtime authentication");
|
||||
return Ok(CurrentClientSetup {
|
||||
auth: Some(auth),
|
||||
auth_owner_generation,
|
||||
auth_revision,
|
||||
api_provider,
|
||||
http_client_factory,
|
||||
redirect_policy,
|
||||
api_auth: resolved_auth.auth,
|
||||
agent_identity_telemetry: None,
|
||||
runtime_id,
|
||||
});
|
||||
}
|
||||
self.provider_client_setup(routing, &self.state.provider)
|
||||
.await
|
||||
}
|
||||
@@ -1284,46 +1162,19 @@ impl ModelClient {
|
||||
routing: ClientRouting,
|
||||
provider: &SharedModelProvider,
|
||||
) -> Result<CurrentClientSetup> {
|
||||
let routing = &routing;
|
||||
codex_model_provider::prepare_provider_request(
|
||||
let routing = match routing {
|
||||
ClientRouting::Workspace => Some(&self.state.workspace_routing),
|
||||
ClientRouting::ConfiguredProvider => None,
|
||||
};
|
||||
codex_model_provider::resolve_request(
|
||||
provider.as_ref(),
|
||||
provider.info().auth.is_none(),
|
||||
|state| async move {
|
||||
let auth = provider.auth().await;
|
||||
let (api_provider, redirect_policy) = match routing {
|
||||
ClientRouting::Workspace => {
|
||||
let resolved = provider
|
||||
.responses_api_provider(&self.state.workspace_routing)
|
||||
.await?;
|
||||
(resolved.provider, resolved.redirect_policy)
|
||||
}
|
||||
ClientRouting::ConfiguredProvider => (
|
||||
provider.api_provider().await?,
|
||||
ClientRedirectPolicy::Default,
|
||||
),
|
||||
};
|
||||
let resolved_auth = provider
|
||||
.api_auth_for_scope(ProviderAuthScope {
|
||||
agent_identity_policy: self.agent_identity_policy,
|
||||
session_source: self.state.session_source.clone(),
|
||||
agent_identity_session_fallback: self
|
||||
.state
|
||||
.agent_identity_session_fallback
|
||||
.clone(),
|
||||
})
|
||||
.await?;
|
||||
Ok(CurrentClientSetup {
|
||||
auth,
|
||||
auth_owner_generation: state.map(|state| state.owner_generation),
|
||||
auth_revision: state.map(|state| state.generation),
|
||||
api_provider,
|
||||
http_client_factory: self.http_client_factory.clone(),
|
||||
redirect_policy,
|
||||
api_auth: resolved_auth.auth,
|
||||
agent_identity_telemetry: resolved_auth.agent_identity_telemetry,
|
||||
runtime_id: None,
|
||||
})
|
||||
},
|
||||
Some(ProviderAuthScope {
|
||||
agent_identity_policy: self.agent_identity_policy,
|
||||
session_source: self.state.session_source.clone(),
|
||||
agent_identity_session_fallback: self.state.agent_identity_session_fallback.clone(),
|
||||
}),
|
||||
routing,
|
||||
self.http_client_factory.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1953,7 +1804,7 @@ impl ResponsesHttpExecutor<'_> {
|
||||
.client
|
||||
.responses_headers(client_setup.auth.as_ref(), &model_info.slug);
|
||||
tracing::Span::current().record("api.path", "/responses");
|
||||
let transport = client_setup.build_api_transport("/responses")?;
|
||||
let transport = client_setup.http_transport("/responses")?;
|
||||
let request_auth_context = AuthRequestTelemetryContext::new(
|
||||
client_setup.auth.as_ref().map(CodexAuth::auth_mode),
|
||||
client_setup.api_auth.as_ref(),
|
||||
|
||||
@@ -70,16 +70,19 @@ async fn selected_runtime_transport_stays_revoked(mode: RuntimeAuthMode) {
|
||||
));
|
||||
let mut client = test_model_client(SessionSource::Exec);
|
||||
client.http_client_factory = factory;
|
||||
Arc::get_mut(&mut client.state).unwrap().provider = create_model_provider(
|
||||
ModelProviderInfo::create_openai_provider(Some(format!("{}/v1", server.uri()))),
|
||||
Some(manager),
|
||||
);
|
||||
let client = client.with_runtime_auth(runtime, mode).unwrap();
|
||||
let setup = client
|
||||
Arc::get_mut(&mut client.state).unwrap().provider = codex_model_provider::ProviderAuthContext {
|
||||
credentials: manager,
|
||||
runtime_auth: Some((runtime, mode)),
|
||||
}
|
||||
.model_provider(ModelProviderInfo::create_openai_provider(
|
||||
/*base_url*/ None,
|
||||
));
|
||||
let mut setup = client
|
||||
.current_client_setup(ClientRouting::Workspace)
|
||||
.await
|
||||
.unwrap();
|
||||
let transport = setup.build_api_transport("/responses").unwrap();
|
||||
setup.api_provider.base_url = format!("{}/v1", server.uri());
|
||||
let transport = setup.http_transport("/responses").unwrap();
|
||||
let request = setup
|
||||
.api_provider
|
||||
.build_request(http::Method::POST, "/responses");
|
||||
@@ -130,15 +133,10 @@ async fn selected_runtime_transport_stays_revoked(mode: RuntimeAuthMode) {
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
#[test_case::test_case(RuntimeAuthMode::DualHeader, false; "dual_header")]
|
||||
#[test_case::test_case(RuntimeAuthMode::AssertionOnly, false; "assertion_only")]
|
||||
#[test_case::test_case(RuntimeAuthMode::DualHeader, true; "runtime_provider_dual_header")]
|
||||
#[test_case::test_case(RuntimeAuthMode::AssertionOnly, true; "runtime_provider_assertion_only")]
|
||||
#[test_case::test_case(RuntimeAuthMode::DualHeader; "dual_header")]
|
||||
#[test_case::test_case(RuntimeAuthMode::AssertionOnly; "assertion_only")]
|
||||
#[tokio::test]
|
||||
async fn realtime_keeps_provider_auth_until_runtime_integration(
|
||||
mode: RuntimeAuthMode,
|
||||
runtime_provider: bool,
|
||||
) {
|
||||
async fn realtime_keeps_provider_auth_until_runtime_integration(mode: RuntimeAuthMode) {
|
||||
let (mut client, _) = model_client_with_counting_attestation(/*include_attestation*/ true);
|
||||
let registration = MockServer::start().await;
|
||||
let manager = client.auth_manager().unwrap();
|
||||
@@ -158,16 +156,13 @@ async fn realtime_keeps_provider_auth_until_runtime_integration(
|
||||
},
|
||||
format!("{}/register", registration.uri()).parse().unwrap(),
|
||||
));
|
||||
if runtime_provider {
|
||||
let info = client.state.provider.info().clone();
|
||||
Arc::get_mut(&mut client.state).unwrap().provider =
|
||||
codex_model_provider::ProviderAuthContext {
|
||||
credentials: manager,
|
||||
runtime_auth: Some((Arc::clone(&runtime), mode)),
|
||||
}
|
||||
.model_provider(info);
|
||||
let info = client.state.provider.info().clone();
|
||||
Arc::get_mut(&mut client.state).unwrap().provider = codex_model_provider::ProviderAuthContext {
|
||||
credentials: manager,
|
||||
runtime_auth: Some((runtime, mode)),
|
||||
}
|
||||
let client = client.with_runtime_auth(runtime, mode).unwrap();
|
||||
.model_provider(info);
|
||||
assert!(client.state.provider.runtime_auth().is_some());
|
||||
let sideband = client
|
||||
.realtime_sideband_headers(http::HeaderMap::new())
|
||||
.await
|
||||
|
||||
@@ -125,10 +125,9 @@ fn test_model_client_with_thread_id(
|
||||
) -> ModelClient {
|
||||
let provider = create_oss_provider_with_base_url("https://example.com/v1", WireApi::Responses);
|
||||
ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
codex_model_provider::create_model_provider(provider, /*auth_manager*/ None),
|
||||
AgentIdentityAuthPolicy::JwtOnly,
|
||||
thread_id,
|
||||
provider,
|
||||
session_source,
|
||||
"test_originator".to_string(),
|
||||
/*model_verbosity*/ None,
|
||||
@@ -227,13 +226,14 @@ async fn workspace_routed_http_rejects_redirects_without_a_routing_header(
|
||||
format!("{}/register", registration.uri()).parse().unwrap(),
|
||||
));
|
||||
let mut client = test_model_client(SessionSource::Exec);
|
||||
Arc::get_mut(&mut client.state).unwrap().provider = create_model_provider(
|
||||
ModelProviderInfo::create_openai_provider(/*base_url*/ None),
|
||||
Some(manager),
|
||||
);
|
||||
if let Some(mode) = runtime_mode {
|
||||
client = client.with_runtime_auth(runtime, mode).unwrap();
|
||||
}
|
||||
Arc::get_mut(&mut client.state).unwrap().provider =
|
||||
codex_model_provider::ProviderAuthContext {
|
||||
credentials: manager,
|
||||
runtime_auth: runtime_mode.map(|mode| (runtime, mode)),
|
||||
}
|
||||
.model_provider(ModelProviderInfo::create_openai_provider(
|
||||
/*base_url*/ None,
|
||||
));
|
||||
let mut setup = client
|
||||
.current_client_setup(super::ClientRouting::Workspace)
|
||||
.await
|
||||
@@ -246,7 +246,7 @@ async fn workspace_routed_http_rejects_redirects_without_a_routing_header(
|
||||
}
|
||||
// Exercise the resolved route's redirect policy against loopback HTTP servers.
|
||||
setup.api_provider.base_url = origin.uri();
|
||||
let transport = setup.build_api_transport("/responses").unwrap();
|
||||
let transport = setup.http_transport("/responses").unwrap();
|
||||
let request = setup
|
||||
.api_provider
|
||||
.build_request(http::Method::POST, "/responses")
|
||||
@@ -2147,10 +2147,9 @@ fn model_client_with_counting_attestation(
|
||||
)
|
||||
};
|
||||
let model_client = ModelClient::new(
|
||||
auth_manager,
|
||||
codex_model_provider::create_model_provider(provider, auth_manager),
|
||||
AgentIdentityAuthPolicy::JwtOnly,
|
||||
ThreadId::new(),
|
||||
provider,
|
||||
SessionSource::Exec,
|
||||
"test_originator".to_string(),
|
||||
/*model_verbosity*/ None,
|
||||
|
||||
@@ -114,6 +114,7 @@ pub(crate) async fn run_codex_thread_interactive(
|
||||
instructions,
|
||||
installation_id: parent_session.installation_id.clone(),
|
||||
auth_manager,
|
||||
runtime_auth: parent_session.services.runtime_auth.clone(),
|
||||
models_manager,
|
||||
git_root_discovery: Arc::clone(&parent_session.services.git_root_discovery),
|
||||
environment_manager: parent_session
|
||||
|
||||
@@ -76,6 +76,7 @@ use codex_features::Features;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_features::MultiAgentV2ConfigToml;
|
||||
use codex_features::NetworkProxyConfigToml;
|
||||
use codex_features::RuntimeAuthMode;
|
||||
use codex_features::SleepToolMode;
|
||||
use codex_features::TokenBudgetConfigToml;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
@@ -1166,6 +1167,8 @@ pub struct Config {
|
||||
pub current_time_reminder: Option<CurrentTimeReminderConfig>,
|
||||
/// How the sleep tool is selected when its feature gate is enabled.
|
||||
pub sleep_tool_mode: SleepToolMode,
|
||||
/// Authentication mode used when runtime agent instances are enabled.
|
||||
pub runtime_auth_mode: RuntimeAuthMode,
|
||||
|
||||
/// Centralized feature flags; source of truth for feature gating.
|
||||
pub features: ManagedFeatures,
|
||||
@@ -4033,6 +4036,15 @@ impl Config {
|
||||
FeatureToml::Config(config) => config.mode,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let runtime_auth_mode = cfg
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.runtime_agent_instances.as_ref())
|
||||
.and_then(|feature| match feature {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => config.mode,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let terminal_resize_reflow = resolve_terminal_resize_reflow_config(&cfg);
|
||||
|
||||
let agent_roles =
|
||||
@@ -4754,6 +4766,7 @@ impl Config {
|
||||
rollout_budget,
|
||||
current_time_reminder,
|
||||
sleep_tool_mode,
|
||||
runtime_auth_mode,
|
||||
features,
|
||||
runtime_feature_defaults: BTreeMap::new(),
|
||||
suppress_unstable_features_warning: cfg
|
||||
|
||||
@@ -27,8 +27,12 @@ pub(crate) fn install(session: &Session, config: &Config) {
|
||||
);
|
||||
let manager = Arc::new(crate::ThreadManager::new(
|
||||
config,
|
||||
Arc::clone(&session.services.auth_manager),
|
||||
Arc::clone(&session.services.models_manager),
|
||||
crate::build_startup_models(
|
||||
config,
|
||||
Arc::clone(&session.services.auth_manager),
|
||||
session.installation_id.clone(),
|
||||
crate::test_support::fixed_model_catalog(Arc::clone(&session.services.models_manager)),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
session.services.turn_environments.environment_manager(),
|
||||
@@ -40,7 +44,6 @@ pub(crate) fn install(session: &Session, config: &Config) {
|
||||
crate::passthrough_image_store(),
|
||||
Arc::clone(&session.services.thread_store),
|
||||
/*agent_graph_store*/ None,
|
||||
session.installation_id.clone(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
));
|
||||
|
||||
@@ -183,10 +183,12 @@ pub use thread_manager::InternalSessionParent;
|
||||
pub use thread_manager::NewThread;
|
||||
pub use thread_manager::PromptCacheKey;
|
||||
pub use thread_manager::StartThreadOptions;
|
||||
pub use thread_manager::StartupModels;
|
||||
pub use thread_manager::ThreadManager;
|
||||
pub use thread_manager::ThreadRegistration;
|
||||
pub use thread_manager::ThreadShutdownReport;
|
||||
pub use thread_manager::build_models_manager;
|
||||
pub use thread_manager::build_startup_models;
|
||||
pub use thread_manager::local_agent_graph_store_from_state_db;
|
||||
pub use thread_manager::passthrough_image_store;
|
||||
pub use thread_store_builder::ThreadStoreBuilder;
|
||||
@@ -281,3 +283,6 @@ pub mod otel_init;
|
||||
// Captured environment bindings can be passed back to ThreadManager by internal reviewers.
|
||||
pub use environment_selection::TurnEnvironmentSnapshot;
|
||||
pub use environment_selection::validate_environment_ids_and_cwds;
|
||||
|
||||
mod runtime_auth;
|
||||
pub use runtime_auth::build_runtime_auth_manager;
|
||||
|
||||
@@ -5,6 +5,7 @@ use codex_exec_server::ExecServerRuntimeOptions;
|
||||
use codex_extension_api::ExtensionRegistry;
|
||||
use codex_extension_api::UserInstructionsProvider;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -44,10 +45,15 @@ pub async fn build_prompt_input(
|
||||
|
||||
let thread_store = ThreadStoreBuilder::from_config(&config, state_db.clone()).build();
|
||||
let installation_id = resolve_installation_id(&config.codex_home).await?;
|
||||
let thread_manager = ThreadManager::new(
|
||||
let startup = crate::build_startup_models(
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
crate::thread_manager::build_models_manager(&config, Arc::clone(&auth_manager)),
|
||||
installation_id,
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
);
|
||||
let thread_manager = ThreadManager::new(
|
||||
&config,
|
||||
startup,
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(
|
||||
@@ -65,7 +71,6 @@ pub async fn build_prompt_input(
|
||||
crate::thread_manager::passthrough_image_store(),
|
||||
thread_store,
|
||||
crate::local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
installation_id,
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//! Construct and share the harness-owned runtime credential context.
|
||||
|
||||
use crate::config::Config;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_runtime_agent_instance::RuntimeAuthManager;
|
||||
use codex_runtime_agent_instance::RuntimeRegistrationMetadata;
|
||||
use codex_runtime_agent_instance::RuntimeRole;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// A later thread may enable runtime auth even when harness startup left it disabled.
|
||||
pub(crate) type RuntimeAuthSlot = Arc<OnceLock<Arc<RuntimeAuthManager>>>;
|
||||
|
||||
/// Share this owner between thread sessions and first-party API clients.
|
||||
/// Construction does not register a runtime or generate its key.
|
||||
pub fn build_runtime_auth_manager(
|
||||
config: &Config,
|
||||
credentials: Arc<AuthManager>,
|
||||
installation_id: &str,
|
||||
) -> Option<Arc<RuntimeAuthManager>> {
|
||||
runtime_for_config(
|
||||
&RuntimeAuthSlot::default(),
|
||||
config,
|
||||
credentials,
|
||||
installation_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn runtime_for_config(
|
||||
slot: &RuntimeAuthSlot,
|
||||
config: &Config,
|
||||
credentials: Arc<AuthManager>,
|
||||
installation_id: &str,
|
||||
) -> Option<Arc<RuntimeAuthManager>> {
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::RuntimeAgentInstances)
|
||||
.then(|| {
|
||||
Arc::clone(slot.get_or_init(|| {
|
||||
Arc::new(RuntimeAuthManager::for_chatgpt(
|
||||
credentials,
|
||||
installation_id.to_owned(),
|
||||
config.chatgpt_base_url.clone(),
|
||||
RuntimeRegistrationMetadata {
|
||||
harness: Some("codex".to_string()),
|
||||
harness_version: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
role: RuntimeRole::Orchestrator,
|
||||
},
|
||||
))
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -218,6 +218,10 @@ impl Session {
|
||||
published: false,
|
||||
};
|
||||
let auth = self.services.auth_manager.auth().await;
|
||||
// Capture after claiming the refresh so a new invalidation cannot be consumed
|
||||
// with the environment snapshot taken before its permissions changed.
|
||||
let environments = self.services.turn_environments.snapshot().await;
|
||||
let environment_selections = environments.all_selections();
|
||||
let desired = self.latest_mcp_desired_state(auth, environments).await;
|
||||
let selected_capability_roots = self
|
||||
.resolve_selected_capability_roots_for_step(&desired.environments)
|
||||
|
||||
@@ -2,6 +2,7 @@ pub(crate) mod startup;
|
||||
|
||||
use crate::PromptCacheKey;
|
||||
use crate::WithTurnExtensionData;
|
||||
use crate::runtime_auth::RuntimeAuthSlot;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
@@ -235,7 +236,6 @@ use crate::thread_rollout_truncation::initial_history_has_prior_user_turns;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_config::ConfigLayerSource;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::CodexErrorDetails;
|
||||
@@ -509,6 +509,7 @@ pub(crate) struct SessionSpawnArgs {
|
||||
pub(crate) instructions: SessionInstructions,
|
||||
pub(crate) installation_id: String,
|
||||
pub(crate) auth_manager: Arc<AuthManager>,
|
||||
pub(crate) runtime_auth: RuntimeAuthSlot,
|
||||
pub(crate) models_manager: SharedModelsManager,
|
||||
pub(crate) git_root_discovery: Arc<GitRootDiscovery>,
|
||||
pub(crate) environment_manager: Arc<EnvironmentManager>,
|
||||
@@ -630,6 +631,7 @@ impl Session {
|
||||
instructions,
|
||||
installation_id,
|
||||
auth_manager,
|
||||
runtime_auth,
|
||||
models_manager,
|
||||
git_root_discovery,
|
||||
environment_manager,
|
||||
@@ -758,12 +760,19 @@ impl Session {
|
||||
};
|
||||
|
||||
let mut config = Arc::new(config);
|
||||
let configured_runtime = crate::runtime_auth::runtime_for_config(
|
||||
&runtime_auth,
|
||||
config.as_ref(),
|
||||
Arc::clone(&auth_manager),
|
||||
&installation_id,
|
||||
);
|
||||
let refresh_strategy = if session_source.is_non_root_agent() {
|
||||
codex_models_manager::manager::RefreshStrategy::Offline
|
||||
} else {
|
||||
codex_models_manager::manager::RefreshStrategy::OnlineIfUncached
|
||||
};
|
||||
if config.model.is_none()
|
||||
if configured_runtime.is_some()
|
||||
|| config.model.is_none()
|
||||
|| !matches!(
|
||||
refresh_strategy,
|
||||
codex_models_manager::manager::RefreshStrategy::Offline
|
||||
@@ -951,12 +960,15 @@ impl Session {
|
||||
let enable_full_access_form_input = !root_capabilities_restricted
|
||||
&& matches!(thread_source.as_ref(), Some(ThreadSource::User))
|
||||
&& client_mcp_extensions.contains(OPENAI_STANDARD_FORM_INPUT_EXTENSION_ID);
|
||||
let provider_auth = codex_model_provider::ProviderAuthContext {
|
||||
credentials: Arc::clone(&auth_manager),
|
||||
runtime_auth: configured_runtime.map(|runtime| (runtime, config.runtime_auth_mode)),
|
||||
};
|
||||
// Replace any inherited context before startup hooks see this thread's effective auth.
|
||||
thread_extension_init.insert(provider_auth.clone());
|
||||
let session_configuration = SessionConfiguration {
|
||||
turn_extension_init,
|
||||
provider: create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
),
|
||||
provider: provider_auth.model_provider(config.model_provider.clone()),
|
||||
step_settings: Arc::new(StepSettings {
|
||||
collaboration_mode,
|
||||
reasoning_summary: config.model_reasoning_summary,
|
||||
@@ -1014,6 +1026,7 @@ impl Session {
|
||||
instructions,
|
||||
installation_id,
|
||||
auth_manager.clone(),
|
||||
runtime_auth,
|
||||
models_manager.clone(),
|
||||
git_root_discovery,
|
||||
model_info,
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::mcp_tool_call::McpToolApprovalContext;
|
||||
use crate::responses_metadata::CodexResponsesMetadata;
|
||||
use crate::responses_metadata::CodexResponsesRequestKind;
|
||||
use crate::responses_metadata::CompactionTurnMetadata;
|
||||
use crate::runtime_auth::RuntimeAuthSlot;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::shell_snapshot::SnapshotCredentialBrokerState;
|
||||
use crate::state::ActiveTurn;
|
||||
@@ -833,6 +834,7 @@ impl Session {
|
||||
mut instructions: SessionInstructions,
|
||||
installation_id: String,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
runtime_auth: RuntimeAuthSlot,
|
||||
models_manager: SharedModelsManager,
|
||||
git_root_discovery: Arc<GitRootDiscovery>,
|
||||
model_info: CatalogModel,
|
||||
@@ -1794,6 +1796,48 @@ impl Session {
|
||||
);
|
||||
let reasoning_effort_override_enabled =
|
||||
config.features.enabled(Feature::ReasoningEffortOverride) && !title_request;
|
||||
let model_client = ModelClient::new(
|
||||
Arc::clone(&session_configuration.provider),
|
||||
if config.features.enabled(Feature::UseAgentIdentity) {
|
||||
AgentIdentityAuthPolicy::ChatGptAuth
|
||||
} else {
|
||||
AgentIdentityAuthPolicy::JwtOnly
|
||||
},
|
||||
thread_id,
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.originator.clone(),
|
||||
config.model_verbosity,
|
||||
config.features.enabled(Feature::ContentItemKinds),
|
||||
reasoning_effort_override_enabled,
|
||||
config.features.enabled(Feature::EnableRequestCompression),
|
||||
config.features.enabled(Feature::RuntimeMetrics),
|
||||
Self::build_model_client_beta_features_header(config.as_ref()),
|
||||
/*concurrent_reasoning_summaries_enabled*/
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::ConcurrentReasoningSummaries),
|
||||
config.features.enabled(Feature::OutputTokenReplay),
|
||||
attestation_provider.clone(),
|
||||
config.http_client_factory(),
|
||||
workspace_routing.as_ref().clone(),
|
||||
extensions.model_request_contributors().to_vec(),
|
||||
)
|
||||
.with_executed_tool_calls(executed_tool_calls.clone())
|
||||
.with_restored_history(matches!(
|
||||
&initial_history,
|
||||
InitialHistory::Resumed(_) | InitialHistory::Forked(_)
|
||||
))
|
||||
.with_session_context(
|
||||
prompt_cache_key,
|
||||
crate::guardian::prompt_cache_key_override_for_review_session(
|
||||
&session_configuration.session_source,
|
||||
session_configuration.parent_thread_id,
|
||||
)
|
||||
.or(fork_cache_key),
|
||||
tx_event.clone(),
|
||||
codex_responses_headers,
|
||||
crate::cyber_access_program::ApiKeyCyberAccessPrograms::from_config(&config),
|
||||
);
|
||||
// Once tracing starts, startup cleanup must own the session before the next await.
|
||||
let rollout_thread_trace = if matches!(
|
||||
session_configuration.session_source,
|
||||
@@ -1832,6 +1876,7 @@ impl Session {
|
||||
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
|
||||
exec_policy,
|
||||
auth_manager: Arc::clone(&auth_manager),
|
||||
runtime_auth,
|
||||
openai_file_upload_client_pool: RouteAwareClientPool::new_without_request_logging(
|
||||
config.http_client_factory(),
|
||||
ClientRouteClass::Api,
|
||||
@@ -1866,49 +1911,7 @@ impl Session {
|
||||
thread_store: Arc::clone(&thread_store),
|
||||
attestation_provider: attestation_provider.clone(),
|
||||
time_provider,
|
||||
model_client: ModelClient::new(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
if config.features.enabled(Feature::UseAgentIdentity) {
|
||||
AgentIdentityAuthPolicy::ChatGptAuth
|
||||
} else {
|
||||
AgentIdentityAuthPolicy::JwtOnly
|
||||
},
|
||||
thread_id,
|
||||
session_configuration.provider.info().clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.originator.clone(),
|
||||
config.model_verbosity,
|
||||
config.features.enabled(Feature::ContentItemKinds),
|
||||
reasoning_effort_override_enabled,
|
||||
config.features.enabled(Feature::EnableRequestCompression),
|
||||
config.features.enabled(Feature::RuntimeMetrics),
|
||||
Self::build_model_client_beta_features_header(config.as_ref()),
|
||||
/*concurrent_reasoning_summaries_enabled*/
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::ConcurrentReasoningSummaries),
|
||||
config.features.enabled(Feature::OutputTokenReplay),
|
||||
attestation_provider,
|
||||
config.http_client_factory(),
|
||||
workspace_routing.as_ref().clone(),
|
||||
extensions.model_request_contributors().to_vec(),
|
||||
)
|
||||
.with_executed_tool_calls(executed_tool_calls.clone())
|
||||
.with_restored_history(matches!(
|
||||
&initial_history,
|
||||
InitialHistory::Resumed(_) | InitialHistory::Forked(_)
|
||||
))
|
||||
.with_session_context(
|
||||
prompt_cache_key,
|
||||
crate::guardian::prompt_cache_key_override_for_review_session(
|
||||
&session_configuration.session_source,
|
||||
session_configuration.parent_thread_id,
|
||||
)
|
||||
.or(fork_cache_key),
|
||||
tx_event.clone(),
|
||||
codex_responses_headers,
|
||||
crate::cyber_access_program::ApiKeyCyberAccessPrograms::from_config(&config),
|
||||
),
|
||||
model_client,
|
||||
executed_tool_calls: executed_tool_calls.clone(),
|
||||
code_mode_service: crate::tools::code_mode::CodeModeService::new(
|
||||
thread_id,
|
||||
|
||||
@@ -895,10 +895,12 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
let thread_id = ThreadId::try_from("00000000-0000-4000-8000-000000000001")
|
||||
.expect("test thread id should be valid");
|
||||
crate::client::ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
codex_model_provider::create_model_provider(
|
||||
ModelProviderInfo::create_openai_provider(/*base_url*/ None),
|
||||
/*auth_manager*/ None,
|
||||
),
|
||||
AgentIdentityAuthPolicy::JwtOnly,
|
||||
thread_id,
|
||||
ModelProviderInfo::create_openai_provider(/* base_url */ /*base_url*/ None),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
"test_originator".to_string(),
|
||||
/*model_verbosity*/ None,
|
||||
@@ -7077,6 +7079,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
|
||||
exec_policy,
|
||||
auth_manager: auth_manager.clone(),
|
||||
runtime_auth: Default::default(),
|
||||
openai_file_upload_client_pool: RouteAwareClientPool::new_without_request_logging(
|
||||
config.http_client_factory(),
|
||||
ClientRouteClass::Api,
|
||||
@@ -7116,10 +7119,12 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
attestation_provider: None,
|
||||
time_provider: Arc::new(crate::current_time::SystemTimeProvider),
|
||||
model_client: ModelClient::new(
|
||||
Some(auth_manager.clone()),
|
||||
codex_model_provider::create_model_provider(
|
||||
session_configuration.provider.info().clone(),
|
||||
Some(auth_manager.clone()),
|
||||
),
|
||||
AgentIdentityAuthPolicy::JwtOnly,
|
||||
thread_id,
|
||||
session_configuration.provider.info().clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.originator.clone(),
|
||||
config.model_verbosity,
|
||||
@@ -7350,6 +7355,7 @@ async fn make_session_with_config_and_rx(
|
||||
SessionInstructions::default(),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
auth_manager,
|
||||
/*runtime_auth*/ Default::default(),
|
||||
models_manager,
|
||||
Arc::default(),
|
||||
model_info,
|
||||
@@ -7489,6 +7495,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
||||
SessionInstructions::default(),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
auth_manager,
|
||||
/*runtime_auth*/ Default::default(),
|
||||
models_manager,
|
||||
Arc::default(),
|
||||
model_info,
|
||||
@@ -9407,6 +9414,7 @@ where
|
||||
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
|
||||
exec_policy,
|
||||
auth_manager: Arc::clone(&auth_manager),
|
||||
runtime_auth: Default::default(),
|
||||
openai_file_upload_client_pool: RouteAwareClientPool::new_without_request_logging(
|
||||
config.http_client_factory(),
|
||||
ClientRouteClass::Api,
|
||||
@@ -9446,10 +9454,12 @@ where
|
||||
attestation_provider: None,
|
||||
time_provider: Arc::new(crate::current_time::SystemTimeProvider),
|
||||
model_client: ModelClient::new(
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
codex_model_provider::create_model_provider(
|
||||
session_configuration.provider.info().clone(),
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
),
|
||||
AgentIdentityAuthPolicy::JwtOnly,
|
||||
thread_id,
|
||||
session_configuration.provider.info().clone(),
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.originator.clone(),
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -1338,6 +1338,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
instructions: Default::default(),
|
||||
installation_id: "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
auth_manager,
|
||||
runtime_auth: Default::default(),
|
||||
models_manager,
|
||||
git_root_discovery: Arc::default(),
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::environment_selection::ThreadEnvironments;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::mcp_tool_exposure::McpHandlerCache;
|
||||
use crate::runtime_auth::RuntimeAuthSlot;
|
||||
use crate::tools::ExecutedToolCalls;
|
||||
use crate::tools::code_mode::CodeModeService;
|
||||
use crate::tools::handlers::ToolSearchHandlerCache;
|
||||
@@ -62,6 +63,7 @@ pub(crate) struct SessionServices {
|
||||
pub(crate) show_raw_agent_reasoning: bool,
|
||||
pub(crate) exec_policy: Arc<ExecPolicyManager>,
|
||||
pub(crate) auth_manager: Arc<AuthManager>,
|
||||
pub(crate) runtime_auth: RuntimeAuthSlot,
|
||||
/// Upload-only clients shared across turns without logging signed blob URLs.
|
||||
pub(crate) openai_file_upload_client_pool: RouteAwareClientPool,
|
||||
pub(crate) models_manager: SharedModelsManager,
|
||||
|
||||
@@ -41,6 +41,30 @@ use crate::responses_metadata::subagent_metadata_kind;
|
||||
use crate::thread_manager;
|
||||
use crate::unified_exec;
|
||||
|
||||
/// Supply fixed model metadata to tests without implementing a provider.
|
||||
pub fn fixed_model_catalog(
|
||||
manager: SharedModelsManager,
|
||||
) -> Arc<dyn codex_model_provider::ModelCatalogFactory> {
|
||||
struct FixedCatalog(SharedModelsManager);
|
||||
|
||||
impl codex_model_provider::ModelCatalogFactory for FixedCatalog {
|
||||
fn build(
|
||||
&self,
|
||||
_provider: codex_model_provider::SharedModelProvider,
|
||||
_config_model_catalog: Option<codex_protocol::openai_models::ModelsResponse>,
|
||||
_api_key_model_discovery_default: bool,
|
||||
) -> SharedModelsManager {
|
||||
Arc::clone(&self.0)
|
||||
}
|
||||
|
||||
fn set_api_key_model_discovery_enabled(&self, enabled: bool) {
|
||||
self.0.set_api_key_model_discovery_enabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(FixedCatalog(manager))
|
||||
}
|
||||
|
||||
static TEST_MODEL_PRESETS: Lazy<Vec<ModelPreset>> = Lazy::new(|| {
|
||||
let mut response = bundled_models_response()
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod managed;
|
||||
mod model_catalog;
|
||||
mod shared_instructions;
|
||||
mod shutdown;
|
||||
|
||||
@@ -72,7 +73,6 @@ use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::OPENAI_PROVIDER_ID;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
@@ -429,6 +429,7 @@ struct ThreadSpawnRequest {
|
||||
startup: Option<Arc<crate::session::startup::SessionStartup>>,
|
||||
options: StartThreadOptions,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
inherited_runtime_auth: Option<crate::runtime_auth::RuntimeAuthSlot>,
|
||||
agent_control: AgentControlInit,
|
||||
parent_thread_id: Option<ThreadId>,
|
||||
parent_originator: Option<String>,
|
||||
@@ -452,6 +453,7 @@ impl ThreadSpawnRequest {
|
||||
startup: None,
|
||||
options,
|
||||
auth_manager,
|
||||
inherited_runtime_auth: None,
|
||||
agent_control: agent_control.into(),
|
||||
parent_thread_id: None,
|
||||
parent_originator: None,
|
||||
@@ -521,8 +523,8 @@ pub(crate) struct ThreadManagerState {
|
||||
thread_id_generator: ThreadIdGenerator,
|
||||
agent_control_factory: Option<AgentControlFactory>,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
models_manager: SharedModelsManager,
|
||||
models_manager_provider: ModelProviderInfo,
|
||||
runtime_auth: crate::runtime_auth::RuntimeAuthSlot,
|
||||
model_catalog: model_catalog::ThreadModelCatalog,
|
||||
git_root_discovery: Arc<GitRootDiscovery>,
|
||||
environment_manager: Arc<EnvironmentManager>,
|
||||
starting_mcp_runtimes: std::sync::Mutex<Vec<std::sync::Weak<AtomicBool>>>,
|
||||
@@ -544,20 +546,9 @@ pub(crate) struct ThreadManagerState {
|
||||
ops_log: Option<SharedCapturedOps>,
|
||||
}
|
||||
|
||||
pub fn build_models_manager(
|
||||
config: &Config,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
) -> SharedModelsManager {
|
||||
let provider = create_model_provider(config.model_provider.clone(), Some(auth_manager));
|
||||
let manager = provider.models_manager(
|
||||
config.codex_home.to_path_buf(),
|
||||
config.model_catalog.clone(),
|
||||
);
|
||||
manager.set_api_key_model_discovery_enabled(
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
);
|
||||
manager
|
||||
}
|
||||
pub use model_catalog::StartupModels;
|
||||
pub use model_catalog::build_models_manager;
|
||||
pub use model_catalog::build_startup_models;
|
||||
|
||||
/// Constructs the default image store that preserves inline images.
|
||||
pub fn passthrough_image_store() -> Arc<dyn AttachmentStore> {
|
||||
@@ -577,8 +568,7 @@ impl ThreadManager {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
config: &Config,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
models_manager: SharedModelsManager,
|
||||
startup: StartupModels,
|
||||
codex_apps_tools_cache: CodexAppsToolsCache,
|
||||
session_source: SessionSource,
|
||||
environment_manager: Arc<EnvironmentManager>,
|
||||
@@ -588,10 +578,15 @@ impl ThreadManager {
|
||||
image_store: Arc<dyn AttachmentStore>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
agent_graph_store: Option<Arc<dyn AgentGraphStore>>,
|
||||
installation_id: String,
|
||||
attestation_provider: Option<Arc<dyn AttestationProvider>>,
|
||||
external_time_provider: Option<Arc<dyn TimeProvider>>,
|
||||
) -> Self {
|
||||
let StartupModels {
|
||||
credentials: auth_manager,
|
||||
runtime_auth,
|
||||
installation_id,
|
||||
catalog: model_catalog,
|
||||
} = startup;
|
||||
let codex_home = config.codex_home.clone();
|
||||
let restriction_product = session_source.restriction_product();
|
||||
let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY);
|
||||
@@ -629,8 +624,7 @@ impl ThreadManager {
|
||||
thread_created_tx,
|
||||
thread_id_generator: default_thread_id_generator(),
|
||||
agent_control_factory: None,
|
||||
models_manager,
|
||||
models_manager_provider: config.model_provider.clone(),
|
||||
model_catalog,
|
||||
git_root_discovery: Arc::default(),
|
||||
environment_manager,
|
||||
starting_mcp_runtimes: std::sync::Mutex::new(Vec::new()),
|
||||
@@ -646,6 +640,7 @@ impl ThreadManager {
|
||||
attestation_provider,
|
||||
external_time_provider,
|
||||
auth_manager,
|
||||
runtime_auth,
|
||||
session_source,
|
||||
installation_id,
|
||||
analytics_events_client,
|
||||
@@ -834,14 +829,24 @@ impl ThreadManager {
|
||||
let agent_graph_store = local_agent_graph_store_from_state_db(state_db.as_ref());
|
||||
Self {
|
||||
state: Arc::new(ThreadManagerState {
|
||||
runtime_auth: Default::default(),
|
||||
threads: Arc::new(RwLock::new(HashMap::new())),
|
||||
shared_thread_instructions: Default::default(),
|
||||
thread_created_tx,
|
||||
thread_id_generator: default_thread_id_generator(),
|
||||
agent_control_factory: None,
|
||||
models_manager_provider: provider.clone(),
|
||||
models_manager: create_model_provider(provider, Some(auth_manager.clone()))
|
||||
.models_manager(codex_home, /*config_model_catalog*/ None),
|
||||
model_catalog: model_catalog::ThreadModelCatalog::new(
|
||||
provider,
|
||||
/*catalog*/ None,
|
||||
Arc::new(codex_models_manager::manager::ModelCatalogSettings::disk(
|
||||
codex_home,
|
||||
)),
|
||||
/*api_key_model_discovery_default*/ false,
|
||||
codex_model_provider::ProviderAuthContext {
|
||||
credentials: Arc::clone(&auth_manager),
|
||||
runtime_auth: None,
|
||||
},
|
||||
),
|
||||
git_root_discovery: Arc::default(),
|
||||
environment_manager,
|
||||
starting_mcp_runtimes: std::sync::Mutex::new(Vec::new()),
|
||||
@@ -982,7 +987,31 @@ impl ThreadManager {
|
||||
}
|
||||
|
||||
pub fn get_models_manager(&self) -> SharedModelsManager {
|
||||
self.state.models_manager.clone()
|
||||
self.state.model_catalog.manager.clone()
|
||||
}
|
||||
|
||||
/// Select the app-server catalog using the same owner as thread requests.
|
||||
pub fn models_manager_for_config(&self, config: &Config) -> SharedModelsManager {
|
||||
let credentials = self.auth_manager();
|
||||
let runtime = crate::runtime_auth::runtime_for_config(
|
||||
&self.state.runtime_auth,
|
||||
config,
|
||||
Arc::clone(&credentials),
|
||||
&self.state.installation_id,
|
||||
);
|
||||
self.state.model_catalog.for_config(
|
||||
config,
|
||||
config.model_catalog.as_ref(),
|
||||
credentials,
|
||||
runtime,
|
||||
/*same_owner*/ true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_api_key_model_discovery_enabled(&self, enabled: bool) {
|
||||
self.state
|
||||
.model_catalog
|
||||
.set_api_key_model_discovery_enabled(enabled);
|
||||
}
|
||||
|
||||
pub async fn list_models(
|
||||
@@ -991,13 +1020,14 @@ impl ThreadManager {
|
||||
http_client_factory: codex_http_client::HttpClientFactory,
|
||||
) -> Vec<ModelPreset> {
|
||||
self.state
|
||||
.models_manager
|
||||
.model_catalog
|
||||
.manager
|
||||
.list_models(refresh_strategy, http_client_factory)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn list_collaboration_modes(&self) -> Vec<CollaborationModeMask> {
|
||||
self.state.models_manager.list_collaboration_modes()
|
||||
self.state.model_catalog.manager.list_collaboration_modes()
|
||||
}
|
||||
|
||||
pub async fn list_thread_ids(&self) -> Vec<ThreadId> {
|
||||
@@ -2081,8 +2111,9 @@ impl ThreadManagerState {
|
||||
(options.client_mcp_extensions, options.prompt_cache_key) =
|
||||
self.inherited_child_settings(parent_thread_id).await;
|
||||
let forked = matches!(options.initial_history, InitialHistory::Forked(_));
|
||||
let mut request =
|
||||
ThreadSpawnRequest::new(options, Arc::clone(&self.auth_manager), agent_control);
|
||||
let (auth_manager, runtime_auth) = self.inherited_child_auth(parent_thread_id).await;
|
||||
let mut request = ThreadSpawnRequest::new(options, auth_manager, agent_control);
|
||||
request.inherited_runtime_auth = Some(runtime_auth);
|
||||
request.parent_thread_id = parent_thread_id;
|
||||
request.inherited_exec_policy = inherited_exec_policy;
|
||||
if forked {
|
||||
@@ -2103,8 +2134,9 @@ impl ThreadManagerState {
|
||||
options.reserved_thread_id.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest("prepared child startup requires a reserved thread ID".into())
|
||||
})?;
|
||||
let mut request =
|
||||
ThreadSpawnRequest::new(options, Arc::clone(&self.auth_manager), agent_control);
|
||||
let (auth_manager, runtime_auth) = self.inherited_child_auth(Some(parent_thread_id)).await;
|
||||
let mut request = ThreadSpawnRequest::new(options, auth_manager, agent_control);
|
||||
request.inherited_runtime_auth = Some(runtime_auth);
|
||||
request.ready_notification = ThreadReadyNotification::Defer;
|
||||
request.parent_thread_id = Some(parent_thread_id);
|
||||
let history_initialization = match &request.options.initial_history {
|
||||
@@ -2175,6 +2207,23 @@ impl ThreadManagerState {
|
||||
Box::pin(self.spawn_thread(request)).await
|
||||
}
|
||||
|
||||
async fn inherited_child_auth(
|
||||
&self,
|
||||
parent_thread_id: Option<ThreadId>,
|
||||
) -> (Arc<AuthManager>, crate::runtime_auth::RuntimeAuthSlot) {
|
||||
let threads = self.threads.read().await;
|
||||
match parent_thread_id.and_then(|id| threads.get(&id)) {
|
||||
Some(parent) => (
|
||||
Arc::clone(&parent.session.services.auth_manager),
|
||||
Arc::clone(&parent.session.services.runtime_auth),
|
||||
),
|
||||
None => (
|
||||
Arc::clone(&self.auth_manager),
|
||||
Arc::clone(&self.runtime_auth),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn inherited_child_settings(
|
||||
&self,
|
||||
parent_thread_id: Option<ThreadId>,
|
||||
@@ -2226,6 +2275,7 @@ impl ThreadManagerState {
|
||||
startup,
|
||||
options,
|
||||
auth_manager,
|
||||
inherited_runtime_auth,
|
||||
agent_control,
|
||||
parent_thread_id,
|
||||
parent_originator,
|
||||
@@ -2527,14 +2577,36 @@ impl ThreadManagerState {
|
||||
} else {
|
||||
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile
|
||||
};
|
||||
let models_manager = if config.model_provider == self.models_manager_provider {
|
||||
Arc::clone(&self.models_manager)
|
||||
// Resume may supply a different credential owner. Keep its runtime separate from the
|
||||
// harness default, and let sessions using that parent's credentials inherit its owner.
|
||||
let runtime_auth = if let Some(runtime_auth) = inherited_runtime_auth {
|
||||
runtime_auth
|
||||
} else if Arc::ptr_eq(&auth_manager, &self.auth_manager) {
|
||||
Arc::clone(&self.runtime_auth)
|
||||
} else {
|
||||
build_models_manager(&config, Arc::clone(&auth_manager))
|
||||
let threads = self.threads.read().await;
|
||||
parent_thread_id
|
||||
.or(forked_from_thread_id)
|
||||
.and_then(|id| threads.get(&id))
|
||||
.filter(|parent| Arc::ptr_eq(&parent.session.services.auth_manager, &auth_manager))
|
||||
.map(|parent| Arc::clone(&parent.session.services.runtime_auth))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let models_manager = models_manager
|
||||
let runtime = crate::runtime_auth::runtime_for_config(
|
||||
&runtime_auth,
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
&self.installation_id,
|
||||
);
|
||||
let manager = self.model_catalog.for_thread(
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
runtime,
|
||||
Arc::ptr_eq(&auth_manager, &self.auth_manager),
|
||||
);
|
||||
let models_manager = manager
|
||||
.for_originator(&startup_context.originator)
|
||||
.unwrap_or(models_manager);
|
||||
.unwrap_or(manager);
|
||||
let spawn_result = Session::spawn(SessionSpawnArgs {
|
||||
attached_thread,
|
||||
startup,
|
||||
@@ -2544,6 +2616,7 @@ impl ThreadManagerState {
|
||||
instructions: startup_context.instructions,
|
||||
installation_id: self.installation_id.clone(),
|
||||
auth_manager,
|
||||
runtime_auth,
|
||||
models_manager,
|
||||
git_root_discovery: Arc::clone(&self.git_root_discovery),
|
||||
environment_manager: Arc::clone(&self.environment_manager),
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
//! Keep each catalog bound to its provider and auth owner while preserving host cache policy.
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::runtime_auth::RuntimeAuthSlot;
|
||||
use crate::runtime_auth::runtime_for_config;
|
||||
use codex_features::Feature;
|
||||
use codex_features::RuntimeAuthMode;
|
||||
use codex_login::AuthManager;
|
||||
use codex_model_provider::ModelCatalogFactory;
|
||||
use codex_model_provider::ProviderAuthContext;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_runtime_agent_instance::RuntimeAuthManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The startup catalog and the credential/runtime owner used to construct it.
|
||||
pub struct StartupModels {
|
||||
pub(super) credentials: Arc<AuthManager>,
|
||||
pub(super) runtime_auth: RuntimeAuthSlot,
|
||||
pub(super) installation_id: String,
|
||||
pub(super) catalog: ThreadModelCatalog,
|
||||
}
|
||||
|
||||
impl StartupModels {
|
||||
/// Share the startup catalog with host discovery and prefetch callers.
|
||||
pub fn models_manager(&self) -> SharedModelsManager {
|
||||
Arc::clone(&self.catalog.manager)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct the catalog and runtime owner together using the host's catalog factory.
|
||||
/// Use `ModelCatalogSettings` for standard provider catalogs with a disk, custom, or no cache.
|
||||
/// Runtime registration remains lazy; this does not register an instance or generate a key.
|
||||
pub fn build_startup_models(
|
||||
config: &Config,
|
||||
credentials: Arc<AuthManager>,
|
||||
installation_id: String,
|
||||
factory: Arc<dyn ModelCatalogFactory>,
|
||||
) -> StartupModels {
|
||||
let runtime_auth = RuntimeAuthSlot::default();
|
||||
let runtime = runtime_for_config(
|
||||
&runtime_auth,
|
||||
config,
|
||||
Arc::clone(&credentials),
|
||||
&installation_id,
|
||||
);
|
||||
let catalog = ThreadModelCatalog::new(
|
||||
config.model_provider.clone(),
|
||||
config.model_catalog.clone(),
|
||||
factory,
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
ProviderAuthContext {
|
||||
credentials: Arc::clone(&credentials),
|
||||
runtime_auth: runtime.map(|runtime| (runtime, config.runtime_auth_mode)),
|
||||
},
|
||||
);
|
||||
StartupModels {
|
||||
credentials,
|
||||
runtime_auth,
|
||||
installation_id,
|
||||
catalog,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ThreadModelCatalog {
|
||||
pub(super) manager: SharedModelsManager,
|
||||
provider: ModelProviderInfo,
|
||||
catalog: Option<ModelsResponse>,
|
||||
factory: Arc<dyn ModelCatalogFactory>,
|
||||
api_key_model_discovery_default: bool,
|
||||
runtime_mode: Option<RuntimeAuthMode>,
|
||||
}
|
||||
|
||||
pub fn build_models_manager(
|
||||
config: &Config,
|
||||
credentials: Arc<AuthManager>,
|
||||
runtime: Option<Arc<RuntimeAuthManager>>,
|
||||
) -> SharedModelsManager {
|
||||
let auth = ProviderAuthContext {
|
||||
credentials,
|
||||
runtime_auth: runtime
|
||||
.filter(|_| config.features.enabled(Feature::RuntimeAgentInstances))
|
||||
.map(|runtime| (runtime, config.runtime_auth_mode)),
|
||||
};
|
||||
ModelCatalogSettings::disk(config.codex_home.to_path_buf()).build(
|
||||
auth.model_provider(config.model_provider.clone()),
|
||||
config.model_catalog.clone(),
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
)
|
||||
}
|
||||
|
||||
impl ThreadModelCatalog {
|
||||
pub(super) fn new(
|
||||
provider: ModelProviderInfo,
|
||||
catalog: Option<ModelsResponse>,
|
||||
factory: Arc<dyn ModelCatalogFactory>,
|
||||
api_key_model_discovery_default: bool,
|
||||
auth: ProviderAuthContext,
|
||||
) -> Self {
|
||||
let runtime_mode = auth.runtime_auth.as_ref().map(|(_, mode)| *mode);
|
||||
let manager = factory.build(
|
||||
auth.model_provider(provider.clone()),
|
||||
catalog.clone(),
|
||||
api_key_model_discovery_default,
|
||||
);
|
||||
Self {
|
||||
manager,
|
||||
provider,
|
||||
catalog,
|
||||
factory,
|
||||
api_key_model_discovery_default,
|
||||
runtime_mode,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn for_thread(
|
||||
&self,
|
||||
config: &Config,
|
||||
credentials: Arc<AuthManager>,
|
||||
runtime: Option<Arc<RuntimeAuthManager>>,
|
||||
same_owner: bool,
|
||||
) -> SharedModelsManager {
|
||||
let models = config.model_catalog.as_ref().or_else(|| {
|
||||
self.catalog
|
||||
.as_ref()
|
||||
.filter(|_| self.provider == config.model_provider)
|
||||
});
|
||||
self.for_config(config, models, credentials, runtime, same_owner)
|
||||
}
|
||||
|
||||
pub(super) fn for_config(
|
||||
&self,
|
||||
config: &Config,
|
||||
models: Option<&ModelsResponse>,
|
||||
credentials: Arc<AuthManager>,
|
||||
runtime: Option<Arc<RuntimeAuthManager>>,
|
||||
same_owner: bool,
|
||||
) -> SharedModelsManager {
|
||||
let runtime = runtime.map(|runtime| (runtime, config.runtime_auth_mode));
|
||||
let api_key_model_discovery_default =
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery);
|
||||
if same_owner
|
||||
&& self.runtime_mode == runtime.as_ref().map(|(_, mode)| *mode)
|
||||
&& self.provider == config.model_provider
|
||||
&& self.catalog.as_ref() == models
|
||||
&& self.api_key_model_discovery_default == api_key_model_discovery_default
|
||||
{
|
||||
return Arc::clone(&self.manager);
|
||||
}
|
||||
let auth = ProviderAuthContext {
|
||||
credentials,
|
||||
runtime_auth: runtime,
|
||||
};
|
||||
self.factory.build(
|
||||
auth.model_provider(config.model_provider.clone()),
|
||||
models.cloned(),
|
||||
api_key_model_discovery_default,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn set_api_key_model_discovery_enabled(&self, enabled: bool) {
|
||||
self.factory.set_api_key_model_discovery_enabled(enabled);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use codex_extension_api::SessionIsolation;
|
||||
use codex_extension_api::empty_extension_registry;
|
||||
use codex_history::InitialHistory;
|
||||
use codex_history::ResumedHistory;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::ResponseItemId;
|
||||
use codex_protocol::approvals::ExecPolicyAmendment;
|
||||
@@ -678,6 +679,128 @@ async fn thread_id_generator_does_not_replace_resumed_thread_id() {
|
||||
.expect("shut down resumed thread");
|
||||
}
|
||||
|
||||
#[test_case::test_case(false; "local_child")]
|
||||
#[test_case::test_case(true; "host_child")]
|
||||
#[tokio::test]
|
||||
async fn child_session_inherits_resumed_parent_auth(host_child: bool) {
|
||||
let home = tempdir().expect("temporary home");
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = home.path().join("codex-home").abs();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
let manager = ThreadManager::with_models_provider_and_home_for_tests(
|
||||
CodexAuth::from_api_key("key-a"),
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
);
|
||||
let original = manager
|
||||
.start_thread(StartThreadOptions {
|
||||
history_mode: Some(ThreadHistoryMode::Legacy),
|
||||
..StartThreadOptions::new(config.clone())
|
||||
})
|
||||
.await
|
||||
.expect("start original thread");
|
||||
original.thread.ensure_rollout_materialized().await;
|
||||
original
|
||||
.thread
|
||||
.flush_rollout()
|
||||
.await
|
||||
.expect("flush original history");
|
||||
original
|
||||
.thread
|
||||
.shutdown_and_wait()
|
||||
.await
|
||||
.expect("stop original thread");
|
||||
manager.remove_thread(&original.thread_id).await;
|
||||
let replacement = AuthManager::from_auth_for_testing_with_home(
|
||||
CodexAuth::from_api_key("key-b"),
|
||||
config.codex_home.to_path_buf(),
|
||||
);
|
||||
let parent = manager
|
||||
.resume_thread_with_history(
|
||||
config.clone(),
|
||||
InitialHistory::Resumed(ResumedHistory {
|
||||
history_revision: None,
|
||||
conversation_id: original.thread_id,
|
||||
history: Arc::new(Vec::new()),
|
||||
rollout_path: original.thread.rollout_path(),
|
||||
}),
|
||||
Arc::clone(&replacement),
|
||||
/*parent_trace*/ None,
|
||||
ClientMcpExtensions::default(),
|
||||
)
|
||||
.await
|
||||
.expect("resume with replacement credentials");
|
||||
let options = StartThreadOptions {
|
||||
reserved_thread_id: host_child.then(|| manager.reserve_thread_id()),
|
||||
session_source: Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: parent.thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
..StartThreadOptions::new(config)
|
||||
};
|
||||
let control = parent
|
||||
.thread
|
||||
.session
|
||||
.services
|
||||
.local_agent_runtime
|
||||
.control(parent.thread.session.session_id());
|
||||
let child = if host_child {
|
||||
manager
|
||||
.state
|
||||
.spawn_host_child_thread(
|
||||
options,
|
||||
control.into(),
|
||||
parent.thread_id,
|
||||
crate::agent::host::PreparedChildInheritance {
|
||||
instructions: parent.thread.session.inherited_instructions().await,
|
||||
exec_policy: None,
|
||||
originator: parent.thread.session.originator().await,
|
||||
attachment_source: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
manager
|
||||
.state
|
||||
.spawn_child_thread(
|
||||
options,
|
||||
control,
|
||||
Some(parent.thread_id),
|
||||
/*inherited_exec_policy*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
.expect("spawn child under the resumed parent");
|
||||
|
||||
assert!(
|
||||
Arc::ptr_eq(&child.thread.session.services.auth_manager, &replacement),
|
||||
"child must use the resumed parent's credentials",
|
||||
);
|
||||
assert!(
|
||||
Arc::ptr_eq(
|
||||
&child.thread.session.services.runtime_auth,
|
||||
&parent.thread.session.services.runtime_auth,
|
||||
),
|
||||
"child must share the resumed parent's runtime owner",
|
||||
);
|
||||
assert!(
|
||||
!Arc::ptr_eq(
|
||||
&child.thread.session.services.runtime_auth,
|
||||
&manager.state.runtime_auth
|
||||
),
|
||||
"replacement-auth descendants must stay separate from the harness runtime",
|
||||
);
|
||||
let report = manager
|
||||
.shutdown_all_threads_bounded(Duration::from_secs(10))
|
||||
.await;
|
||||
assert_eq!(report.completed.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn child_session_inherits_client_mcp_extensions() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
@@ -1250,8 +1373,12 @@ async fn mcp_invalidation_refreshes_threads_that_are_still_starting() {
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
|
||||
let manager = Arc::new(ThreadManager::new(
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -1261,7 +1388,6 @@ async fn mcp_invalidation_refreshes_threads_that_are_still_starting() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
));
|
||||
@@ -1918,8 +2044,12 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -1929,7 +2059,6 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2365,8 +2494,12 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2376,7 +2509,6 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2525,8 +2657,12 @@ async fn explicit_installation_id_skips_codex_home_file() {
|
||||
let thread_store = ThreadStoreBuilder::from_config(&config, state_db.clone()).build();
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
installation_id.clone(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2536,7 +2672,6 @@ async fn explicit_installation_id_skips_codex_home_file() {
|
||||
passthrough_image_store(),
|
||||
thread_store,
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
installation_id.clone(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2569,8 +2704,12 @@ async fn resume_active_thread_from_rollout_returns_running_thread() {
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2580,7 +2719,6 @@ async fn resume_active_thread_from_rollout_returns_running_thread() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2635,8 +2773,12 @@ async fn resume_stopped_thread_from_rollout_spawns_new_thread() {
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2646,7 +2788,6 @@ async fn resume_stopped_thread_from_rollout_spawns_new_thread() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2708,8 +2849,12 @@ async fn resume_stopped_thread_from_rollout_preserves_thread_source() {
|
||||
let thread_store = ThreadStoreBuilder::from_config(&config, state_db.clone()).build();
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2719,7 +2864,6 @@ async fn resume_stopped_thread_from_rollout_preserves_thread_source() {
|
||||
passthrough_image_store(),
|
||||
thread_store,
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2795,8 +2939,12 @@ async fn subtree_listing_uses_injected_graph_store_without_state_db() {
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2806,7 +2954,6 @@ async fn subtree_listing_uses_injected_graph_store_without_state_db() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
Some(agent_graph_store),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2843,8 +2990,12 @@ async fn rollout_path_resume_and_fork_read_history_through_thread_store() {
|
||||
.expect("configured in-memory store");
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2854,7 +3005,6 @@ async fn rollout_path_resume_and_fork_read_history_through_thread_store() {
|
||||
passthrough_image_store(),
|
||||
thread_store.clone(),
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -2953,8 +3103,12 @@ async fn metadata_update_without_result_reads_only_when_the_caller_needs_the_thr
|
||||
.expect("configured in-memory store");
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -2964,7 +3118,6 @@ async fn metadata_update_without_result_reads_only_when_the_caller_needs_the_thr
|
||||
passthrough_image_store(),
|
||||
thread_store.clone(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -3080,8 +3233,12 @@ async fn new_uses_active_provider_for_model_refresh() {
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -3091,7 +3248,6 @@ async fn new_uses_active_provider_for_model_refresh() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -3157,7 +3313,7 @@ async fn new_uses_active_provider_for_model_refresh() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn injected_models_manager_controls_refresh_policy() {
|
||||
async fn startup_catalog_cache_policy_controls_refresh() {
|
||||
let server = MockServer::start().await;
|
||||
let _ = mount_models_once(&server, ModelsResponse { models: vec![] }).await;
|
||||
let _ = mount_models_once(&server, ModelsResponse { models: vec![] }).await;
|
||||
@@ -3172,15 +3328,14 @@ async fn injected_models_manager_controls_refresh_policy() {
|
||||
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
Some(Arc::clone(&auth_manager)),
|
||||
);
|
||||
let models_manager = provider.models_manager_without_cache(config.model_catalog.clone());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager,
|
||||
models_manager,
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::default()),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Custom("test-embedder".to_string()),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -3190,7 +3345,6 @@ async fn injected_models_manager_controls_refresh_policy() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -3460,8 +3614,12 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
|
||||
let state_db = init_state_db(&config).await;
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -3471,7 +3629,6 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, state_db.clone()).build(),
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -3572,8 +3729,12 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
let state_db = init_state_db(&config).await;
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -3583,7 +3744,6 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, state_db.clone()).build(),
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
@@ -3677,8 +3837,12 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
|
||||
let state_db = init_state_db(&config).await;
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
build_models_manager(&config, auth_manager.clone()),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -3688,7 +3852,6 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
|
||||
passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, state_db.clone()).build(),
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
TEST_INSTALLATION_ID.to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
|
||||
@@ -37,6 +37,7 @@ use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider_info::built_in_model_providers;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_models_manager::manager::StaticModelsManager;
|
||||
use codex_otel::MULTI_AGENT_SPAWN_FAILURE_METRIC;
|
||||
use codex_otel::MetricsClient;
|
||||
@@ -4319,8 +4320,12 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
crate::thread_manager::build_models_manager(&config, auth_manager),
|
||||
crate::build_startup_models(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
),
|
||||
crate::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
@@ -4330,7 +4335,6 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
|
||||
crate::thread_manager::passthrough_image_store(),
|
||||
ThreadStoreBuilder::from_config(&config, state_db.clone()).build(),
|
||||
local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user