Tell model about credentialed routes

This commit is contained in:
Winston Howes
2026-06-10 17:48:11 -07:00
parent bed7218b7f
commit e1b720edfb
11 changed files with 446 additions and 72 deletions
+1
View File
@@ -2589,6 +2589,7 @@ dependencies = [
"codex-app-server-protocol",
"codex-apply-patch",
"codex-async-utils",
"codex-backend-client",
"codex-code-mode",
"codex-config",
"codex-connectors",
+1
View File
@@ -29,6 +29,7 @@ codex-api = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-apply-patch = { workspace = true }
codex-async-utils = { workspace = true }
codex-backend-client = { workspace = true }
codex-code-mode = { workspace = true }
codex-connectors = { workspace = true }
codex-context-fragments = { workspace = true }
+1
View File
@@ -154,6 +154,7 @@ pub use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
pub use codex_sandboxing::system_bwrap_warning;
pub use managed_features::ManagedFeatures;
pub(crate) use network_proxy_spec::NetworkProxyRuntimeState;
pub use network_proxy_spec::NetworkProxySpec;
pub use network_proxy_spec::StartedNetworkProxy;
pub(crate) use permissions::is_builtin_permission_profile_name;
+34 -2
View File
@@ -34,6 +34,11 @@ pub struct StartedNetworkProxy {
_handle: NetworkProxyHandle,
}
pub(crate) struct NetworkProxyRuntimeState {
pub(crate) state: ConfigState,
pub(crate) reloader: Arc<dyn ConfigReloader>,
}
impl StartedNetworkProxy {
fn new(proxy: NetworkProxy, handle: NetworkProxyHandle) -> Self {
Self {
@@ -128,7 +133,33 @@ impl NetworkProxySpec {
enable_network_approval_flow: bool,
audit_metadata: NetworkProxyAuditMetadata,
) -> std::io::Result<StartedNetworkProxy> {
let state = self.build_state_with_audit_metadata(audit_metadata)?;
let state = self.build_config_state_for_spec()?;
self.start_proxy_with_runtime_state(
permission_profile,
policy_decider,
blocked_request_observer,
enable_network_approval_flow,
audit_metadata,
NetworkProxyRuntimeState {
state: state.clone(),
reloader: Arc::new(StaticNetworkProxyReloader::new(state)),
},
)
.await
}
pub(crate) async fn start_proxy_with_runtime_state(
&self,
permission_profile: &PermissionProfile,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
enable_network_approval_flow: bool,
audit_metadata: NetworkProxyAuditMetadata,
runtime_state: NetworkProxyRuntimeState,
) -> std::io::Result<StartedNetworkProxy> {
let NetworkProxyRuntimeState { state, reloader } = runtime_state;
let state =
NetworkProxyState::with_reloader_and_audit_metadata(state, reloader, audit_metadata);
let mut builder = NetworkProxy::builder().state(Arc::new(state));
if enable_network_approval_flow && !self.hard_deny_allowlist_misses {
if let Some(policy_decider) = policy_decider {
@@ -191,6 +222,7 @@ impl NetworkProxySpec {
})
}
#[cfg(test)]
fn build_state_with_audit_metadata(
&self,
audit_metadata: NetworkProxyAuditMetadata,
@@ -204,7 +236,7 @@ impl NetworkProxySpec {
))
}
fn build_config_state_for_spec(&self) -> std::io::Result<ConfigState> {
pub(crate) fn build_config_state_for_spec(&self) -> std::io::Result<ConfigState> {
build_config_state(self.config.clone(), self.constraints.clone()).map_err(|err| {
std::io::Error::other(format!("failed to build network proxy state: {err}"))
})
+126
View File
@@ -0,0 +1,126 @@
use anyhow::Result;
use codex_backend_client::Client as BackendClient;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_network_proxy::CredentialedRoute;
use codex_network_proxy::CredentialedRouteProxyHeader;
use codex_network_proxy::CredentialedRoutesConfig;
use codex_network_proxy::CredentialedRoutesSource;
use http::HeaderMap;
use std::collections::BTreeSet;
use std::sync::Arc;
use tracing::debug;
use tracing::warn;
const MAX_CREDENTIALED_ROUTE_INSTRUCTION_CHARS: usize = 8_000;
const MAX_CREDENTIALED_ROUTE_INSTRUCTION_PREFIXES: usize = 100;
const OMITTED_CREDENTIALED_ROUTES_INSTRUCTION: &str =
"\n- [additional credentialed routes omitted]";
pub(crate) async fn load_for_session(
chatgpt_base_url: &str,
auth: Option<&CodexAuth>,
) -> CredentialedRoutesConfig {
match fetch(chatgpt_base_url, auth).await {
Ok(credentialed_routes) => credentialed_routes,
Err(err) => {
warn!(error = %err, "failed to load credentialed routes for session");
CredentialedRoutesConfig::default()
}
}
}
pub(crate) fn developer_instructions(
credentialed_routes: &CredentialedRoutesConfig,
) -> Option<String> {
let route_prefixes = credentialed_routes
.routes
.iter()
.map(|route| route.base_url.clone())
.collect::<BTreeSet<_>>();
if route_prefixes.is_empty() {
return None;
}
let header = "The managed network proxy automatically attaches stored credentials when you call these HTTPS URL prefixes directly:";
let mut instructions = header.to_string();
let mut omitted_prefixes = false;
let route_prefix_count = route_prefixes.len();
for (index, route_prefix) in route_prefixes.into_iter().enumerate() {
let route_prefix = format!("\n- {route_prefix}");
let omitted_suffix_len = if index + 1 < route_prefix_count {
OMITTED_CREDENTIALED_ROUTES_INSTRUCTION.len()
} else {
0
};
if index == MAX_CREDENTIALED_ROUTE_INSTRUCTION_PREFIXES
|| instructions.len() + route_prefix.len() + omitted_suffix_len
> MAX_CREDENTIALED_ROUTE_INSTRUCTION_CHARS
{
omitted_prefixes = true;
break;
}
instructions.push_str(&route_prefix);
}
if omitted_prefixes {
instructions.push_str(OMITTED_CREDENTIALED_ROUTES_INSTRUCTION);
}
Some(instructions)
}
async fn fetch(
chatgpt_base_url: &str,
auth: Option<&CodexAuth>,
) -> Result<CredentialedRoutesConfig> {
let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) else {
return Ok(CredentialedRoutesConfig::default());
};
let client = BackendClient::from_auth(chatgpt_base_url.to_string(), auth)?;
let response = client.list_credential_routes().await?;
debug!(
credentialed_routes = response.routes.len(),
"loaded credentialed routes for session"
);
Ok(CredentialedRoutesConfig {
routes: response
.routes
.into_iter()
.map(|route| CredentialedRoute {
connector_id: route.connector_id,
link_id: route.link_id,
base_url: route.base_url,
})
.collect(),
proxy_headers: credentialed_route_proxy_headers(client.credential_routes_proxy_headers()),
proxy_url: Some(client.credential_routes_proxy_url()),
})
}
pub(crate) fn source(
chatgpt_base_url: String,
auth_manager: Arc<AuthManager>,
) -> Arc<dyn CredentialedRoutesSource> {
Arc::new(move || {
let chatgpt_base_url = chatgpt_base_url.clone();
let auth_manager = Arc::clone(&auth_manager);
async move {
let auth = auth_manager.auth().await;
fetch(&chatgpt_base_url, auth.as_ref()).await
}
})
}
#[cfg(test)]
#[path = "credentialed_routes_tests.rs"]
mod tests;
fn credentialed_route_proxy_headers(headers: HeaderMap) -> Vec<CredentialedRouteProxyHeader> {
headers
.iter()
.map(|(name, value)| CredentialedRouteProxyHeader {
name: name.clone(),
value: value.clone(),
})
.collect()
}
@@ -0,0 +1,52 @@
use super::*;
#[test]
fn developer_instructions_deduplicate_and_sort_route_prefixes() {
let credentialed_routes = CredentialedRoutesConfig {
routes: vec![
CredentialedRoute {
connector_id: "connector_b".to_string(),
link_id: "link_b".to_string(),
base_url: "https://b.example.com/v1".to_string(),
},
CredentialedRoute {
connector_id: "connector_a".to_string(),
link_id: "link_a".to_string(),
base_url: "https://a.example.com/v1".to_string(),
},
CredentialedRoute {
connector_id: "connector_a".to_string(),
link_id: "link_a".to_string(),
base_url: "https://a.example.com/v1".to_string(),
},
],
..CredentialedRoutesConfig::default()
};
assert_eq!(
developer_instructions(&credentialed_routes),
Some(
"The managed network proxy automatically attaches stored credentials when you call these HTTPS URL prefixes directly:\n- https://a.example.com/v1\n- https://b.example.com/v1".to_string()
)
);
}
#[test]
fn developer_instructions_cap_route_prefixes() {
let credentialed_routes = CredentialedRoutesConfig {
routes: (0..=MAX_CREDENTIALED_ROUTE_INSTRUCTION_PREFIXES)
.map(|index| CredentialedRoute {
connector_id: format!("connector_{index}"),
link_id: format!("link_{index}"),
base_url: format!("https://{index}.example.com/v1"),
})
.collect(),
..CredentialedRoutesConfig::default()
};
let instructions =
developer_instructions(&credentialed_routes).expect("routes should render instructions");
assert!(instructions.len() <= MAX_CREDENTIALED_ROUTE_INSTRUCTION_CHARS);
assert!(instructions.contains("[additional credentialed routes omitted]"));
}
+1
View File
@@ -34,6 +34,7 @@ pub mod config;
pub mod connectors;
pub mod context;
mod context_manager;
mod credentialed_routes;
mod environment_selection;
pub mod exec;
pub mod exec_env;
+117 -24
View File
@@ -899,6 +899,18 @@ async fn thread_title_from_thread_store(
(!title.is_empty() && thread.preview.trim() != title).then(|| title.to_string())
}
struct ManagedNetworkProxyStartRequest<'a> {
spec: &'a crate::config::NetworkProxySpec,
credentialed_routes: codex_network_proxy::CredentialedRoutesConfig,
credentialed_routes_source: Arc<dyn codex_network_proxy::CredentialedRoutesSource>,
exec_policy: &'a codex_execpolicy::Policy,
permission_profile: &'a PermissionProfile,
network_policy_decider: Option<Arc<dyn codex_network_proxy::NetworkPolicyDecider>>,
blocked_request_observer: Option<Arc<dyn codex_network_proxy::BlockedRequestObserver>>,
managed_network_requirements_enabled: bool,
audit_metadata: NetworkProxyAuditMetadata,
}
impl Session {
pub(crate) async fn app_server_client_metadata(&self) -> AppServerClientMetadata {
let state = self.state.lock().await;
@@ -946,14 +958,23 @@ impl Session {
}
async fn start_managed_network_proxy(
spec: &crate::config::NetworkProxySpec,
exec_policy: &codex_execpolicy::Policy,
permission_profile: &PermissionProfile,
network_policy_decider: Option<Arc<dyn codex_network_proxy::NetworkPolicyDecider>>,
blocked_request_observer: Option<Arc<dyn codex_network_proxy::BlockedRequestObserver>>,
managed_network_requirements_enabled: bool,
audit_metadata: NetworkProxyAuditMetadata,
) -> anyhow::Result<(StartedNetworkProxy, SessionNetworkProxyRuntime)> {
request: ManagedNetworkProxyStartRequest<'_>,
) -> anyhow::Result<(
StartedNetworkProxy,
SessionNetworkProxyRuntime,
Arc<codex_network_proxy::CredentialedRoutesReloader>,
)> {
let ManagedNetworkProxyStartRequest {
spec,
credentialed_routes,
credentialed_routes_source,
exec_policy,
permission_profile,
network_policy_decider,
blocked_request_observer,
managed_network_requirements_enabled,
audit_metadata,
} = request;
let spec = spec
.with_exec_policy_network_rules(exec_policy)
.map_err(|err| {
@@ -963,13 +984,31 @@ impl Session {
err
})
.unwrap_or_else(|_| spec.clone());
let credentialed_routes_reloader =
Arc::new(codex_network_proxy::CredentialedRoutesReloader::new(
spec.build_config_state_for_spec().map_err(|err| {
anyhow::anyhow!("failed to build managed proxy base state: {err}")
})?,
credentialed_routes,
credentialed_routes_source,
));
let state =
codex_network_proxy::ConfigReloader::reload_now(credentialed_routes_reloader.as_ref())
.await
.map_err(|err| {
anyhow::anyhow!("failed to build credentialed route proxy state: {err}")
})?;
let network_proxy = spec
.start_proxy(
.start_proxy_with_runtime_state(
permission_profile,
network_policy_decider,
blocked_request_observer,
managed_network_requirements_enabled,
audit_metadata,
crate::config::NetworkProxyRuntimeState {
state,
reloader: credentialed_routes_reloader.clone(),
},
)
.await
.map_err(|err| anyhow::anyhow!("failed to start managed network proxy: {err}"))?;
@@ -980,7 +1019,11 @@ impl Session {
socks_addr: proxy.socks_addr().to_string(),
}
};
Ok((network_proxy, session_network_proxy))
Ok((
network_proxy,
session_network_proxy,
credentialed_routes_reloader,
))
}
async fn refresh_managed_network_proxy_for_current_permission_profile(&self) {
@@ -1000,6 +1043,7 @@ impl Session {
.cloned()
else {
self.services.network_proxy.store(None);
self.services.credentialed_routes_reloader.store(None);
return;
};
@@ -1023,31 +1067,71 @@ impl Session {
}
};
if let Some(started_proxy) = self.services.network_proxy.load_full() {
if let Err(err) = spec.apply_to_started_proxy(started_proxy.as_ref()).await {
let update_result = if let Some(credentialed_routes_reloader) =
self.services.credentialed_routes_reloader.load_full()
{
match spec.build_config_state_for_spec() {
Ok(base_state) => match credentialed_routes_reloader
.replace_base_state(base_state)
.await
{
Ok(state) => started_proxy
.proxy()
.replace_config_state(state)
.await
.map_err(std::io::Error::other),
Err(err) => Err(std::io::Error::other(err)),
},
Err(err) => Err(err),
}
} else {
spec.apply_to_started_proxy(started_proxy.as_ref()).await
};
if let Err(err) = update_result {
warn!("failed to refresh managed network proxy for sandbox change: {err}");
}
return;
}
match Self::start_managed_network_proxy(
&spec,
current_exec_policy.as_ref(),
&session_configuration.permission_profile(),
/*network_policy_decider*/ None,
self.services
.managed_network_requirements_configured
.then(|| {
build_blocked_request_observer(Arc::clone(&self.services.network_approval))
}),
self.services.managed_network_requirements_configured,
self.services.network_proxy_audit_metadata.clone(),
let auth = self.services.auth_manager.auth().await;
let credentialed_routes = crate::credentialed_routes::load_for_session(
&session_configuration
.original_config_do_not_use
.chatgpt_base_url,
auth.as_ref(),
)
.await;
let credentialed_routes_source = crate::credentialed_routes::source(
session_configuration
.original_config_do_not_use
.chatgpt_base_url
.clone(),
Arc::clone(&self.services.auth_manager),
);
match Self::start_managed_network_proxy(ManagedNetworkProxyStartRequest {
spec: &spec,
credentialed_routes,
credentialed_routes_source,
exec_policy: current_exec_policy.as_ref(),
permission_profile: &session_configuration.permission_profile(),
network_policy_decider: None,
blocked_request_observer: self.services.managed_network_requirements_configured.then(
|| build_blocked_request_observer(Arc::clone(&self.services.network_approval)),
),
managed_network_requirements_enabled: self
.services
.managed_network_requirements_configured,
audit_metadata: self.services.network_proxy_audit_metadata.clone(),
})
.await
{
Ok((started_proxy, _session_network_proxy)) => {
Ok((started_proxy, _session_network_proxy, credentialed_routes_reloader)) => {
self.services
.network_proxy
.store(Some(Arc::new(started_proxy)));
self.services
.credentialed_routes_reloader
.store(Some(credentialed_routes_reloader));
}
Err(err) => {
warn!("failed to start managed network proxy for sandbox change: {err}");
@@ -2810,6 +2894,15 @@ impl Session {
{
developer_sections.push(developer_instructions.to_string());
}
if let Some(credentialed_routes_reloader) =
self.services.credentialed_routes_reloader.load_full()
&& let Some(credentialed_route_instructions) =
crate::credentialed_routes::developer_instructions(
&credentialed_routes_reloader.current_routes().await,
)
{
developer_sections.push(credentialed_route_instructions);
}
// Add developer instructions from collaboration_mode if they exist and are non-empty
if turn_context.config.include_collaboration_mode_instructions
&& let Some(collab_instructions) =
+32 -12
View File
@@ -893,18 +893,31 @@ impl Session {
Arc::clone(network_policy_decider_session),
)
});
let (network_proxy, session_network_proxy) =
let credentialed_routes = if config.permissions.network.is_some() {
crate::credentialed_routes::load_for_session(&config.chatgpt_base_url, auth).await
} else {
codex_network_proxy::CredentialedRoutesConfig::default()
};
let credentialed_routes_source = crate::credentialed_routes::source(
config.chatgpt_base_url.clone(),
Arc::clone(&auth_manager),
);
let (network_proxy, session_network_proxy, credentialed_routes_reloader) =
if let Some(spec) = config.permissions.network.as_ref() {
let current_exec_policy = exec_policy.current();
let (network_proxy, session_network_proxy) = Self::start_managed_network_proxy(
spec,
current_exec_policy.as_ref(),
config.permissions.permission_profile(),
network_policy_decider.as_ref().map(Arc::clone),
blocked_request_observer.as_ref().map(Arc::clone),
managed_network_requirements_configured,
network_proxy_audit_metadata.clone(),
)
let (network_proxy, session_network_proxy, credentialed_routes_reloader) =
Self::start_managed_network_proxy(ManagedNetworkProxyStartRequest {
spec,
credentialed_routes,
credentialed_routes_source: Arc::clone(&credentialed_routes_source),
exec_policy: current_exec_policy.as_ref(),
permission_profile: config.permissions.permission_profile(),
network_policy_decider: network_policy_decider.as_ref().map(Arc::clone),
blocked_request_observer: blocked_request_observer.as_ref().map(Arc::clone),
managed_network_requirements_enabled:
managed_network_requirements_configured,
audit_metadata: network_proxy_audit_metadata.clone(),
})
.instrument(info_span!(
"session_init.network_proxy",
otel.name = "session_init.network_proxy",
@@ -912,9 +925,13 @@ impl Session {
managed_network_requirements_enabled,
))
.await?;
(Some(network_proxy), Some(session_network_proxy))
(
Some(network_proxy),
Some(session_network_proxy),
Some(credentialed_routes_reloader),
)
} else {
(None, None)
(None, None, None)
};
let hooks =
@@ -1006,6 +1023,9 @@ impl Session {
thread_extension_data,
agent_control,
network_proxy: arc_swap::ArcSwapOption::from(network_proxy.map(Arc::new)),
credentialed_routes_reloader: arc_swap::ArcSwapOption::from(
credentialed_routes_reloader,
),
network_proxy_audit_metadata,
managed_network_requirements_configured,
network_approval: Arc::clone(&network_approval),
+79 -34
View File
@@ -174,6 +174,28 @@ use std::time::Duration as StdDuration;
mod guardian_tests;
fn empty_credentialed_routes_source() -> Arc<dyn codex_network_proxy::CredentialedRoutesSource> {
Arc::new(|| async { Ok(codex_network_proxy::CredentialedRoutesConfig::default()) })
}
fn managed_network_proxy_start_request<'a>(
spec: &'a crate::config::NetworkProxySpec,
exec_policy: &'a Policy,
permission_profile: &'a PermissionProfile,
) -> ManagedNetworkProxyStartRequest<'a> {
ManagedNetworkProxyStartRequest {
spec,
credentialed_routes: codex_network_proxy::CredentialedRoutesConfig::default(),
credentialed_routes_source: empty_credentialed_routes_source(),
exec_policy,
permission_profile,
network_policy_decider: None,
blocked_request_observer: None,
managed_network_requirements_enabled: false,
audit_metadata: crate::config::NetworkProxyAuditMetadata::default(),
}
}
struct InstructionsTestCase {
slug: &'static str,
expects_apply_patch_description: bool,
@@ -708,14 +730,8 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho
/*justification*/ None,
)?;
let (started_proxy, _) = Session::start_managed_network_proxy(
&spec,
&exec_policy,
&permission_profile,
/*network_policy_decider*/ None,
/*blocked_request_observer*/ None,
/*managed_network_requirements_enabled*/ false,
crate::config::NetworkProxyAuditMetadata::default(),
let (started_proxy, _, _) = Session::start_managed_network_proxy(
managed_network_proxy_start_request(&spec, &exec_policy, &permission_profile),
)
.await?;
@@ -753,14 +769,8 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
/*justification*/ None,
)?;
let (started_proxy, _) = Session::start_managed_network_proxy(
&spec,
&exec_policy,
&permission_profile,
/*network_policy_decider*/ None,
/*blocked_request_observer*/ None,
/*managed_network_requirements_enabled*/ false,
crate::config::NetworkProxyAuditMetadata::default(),
let (started_proxy, _, _) = Session::start_managed_network_proxy(
managed_network_proxy_start_request(&spec, &exec_policy, &permission_profile),
)
.await?;
@@ -793,16 +803,17 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R
}
});
let (started_proxy, _) = Session::start_managed_network_proxy(
&spec,
&exec_policy,
&full_access_permission_profile,
Some(network_policy_decider),
/*blocked_request_observer*/ None,
/*managed_network_requirements_enabled*/ true,
crate::config::NetworkProxyAuditMetadata::default(),
)
.await?;
let (started_proxy, _, _) =
Session::start_managed_network_proxy(ManagedNetworkProxyStartRequest {
network_policy_decider: Some(network_policy_decider),
managed_network_requirements_enabled: true,
..managed_network_proxy_start_request(
&spec,
&exec_policy,
&full_access_permission_profile,
)
})
.await?;
let spec = spec.recompute_for_permission_profile(&PermissionProfile::workspace_write())?;
spec.apply_to_started_proxy(&started_proxy).await?;
@@ -863,14 +874,9 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
Some(requirements),
&initial_permission_profile,
)?;
let (started_proxy, _) = Session::start_managed_network_proxy(
&spec,
&Policy::empty(),
&initial_permission_profile,
/*network_policy_decider*/ None,
/*blocked_request_observer*/ None,
/*managed_network_requirements_enabled*/ false,
crate::config::NetworkProxyAuditMetadata::default(),
let exec_policy = Policy::empty();
let (started_proxy, _, _) = Session::start_managed_network_proxy(
managed_network_proxy_start_request(&spec, &exec_policy, &initial_permission_profile),
)
.await?;
assert_eq!(
@@ -4868,6 +4874,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
thread_extension_data: codex_extension_api::ExtensionData::new(thread_id.to_string()),
agent_control,
network_proxy: arc_swap::ArcSwapOption::from(None),
credentialed_routes_reloader: arc_swap::ArcSwapOption::from(None),
network_proxy_audit_metadata: crate::config::NetworkProxyAuditMetadata::default(),
managed_network_requirements_configured: false,
network_approval: Arc::clone(&network_approval),
@@ -6946,6 +6953,7 @@ where
thread_extension_data: codex_extension_api::ExtensionData::new(thread_id.to_string()),
agent_control,
network_proxy: arc_swap::ArcSwapOption::from(None),
credentialed_routes_reloader: arc_swap::ArcSwapOption::from(None),
network_proxy_audit_metadata: crate::config::NetworkProxyAuditMetadata::default(),
managed_network_requirements_configured: false,
network_approval: Arc::clone(&network_approval),
@@ -7513,6 +7521,43 @@ async fn build_initial_context_adds_multi_agent_v2_root_usage_hint_as_developer_
);
}
#[tokio::test]
async fn build_initial_context_adds_credentialed_route_instructions_as_developer_message() {
let (session, turn_context) = make_session_and_context().await;
let base_state = codex_network_proxy::build_config_state(
codex_network_proxy::NetworkProxyConfig::default(),
codex_network_proxy::NetworkProxyConstraints::default(),
)
.expect("credentialed route test state should compile");
let credentialed_routes_reloader =
Arc::new(codex_network_proxy::CredentialedRoutesReloader::new(
base_state,
codex_network_proxy::CredentialedRoutesConfig {
routes: vec![codex_network_proxy::CredentialedRoute {
connector_id: "connector_123".to_string(),
link_id: "link_123".to_string(),
base_url: "https://api.example.com/v1".to_string(),
}],
..codex_network_proxy::CredentialedRoutesConfig::default()
},
empty_credentialed_routes_source(),
));
session
.services
.credentialed_routes_reloader
.store(Some(credentialed_routes_reloader));
let initial_context = session.build_initial_context(&turn_context).await;
assert!(
developer_input_texts(&initial_context)
.iter()
.any(|text| text.contains("https://api.example.com/v1")),
"expected credentialed route developer instructions, got {:?}",
developer_message_texts(&initial_context)
);
}
#[tokio::test]
async fn build_initial_context_adds_multi_agent_v2_subagent_usage_hint_as_developer_message() {
let (session, mut turn_context) =
+2
View File
@@ -69,6 +69,8 @@ pub(crate) struct SessionServices {
pub(crate) thread_extension_data: ExtensionData,
pub(crate) agent_control: AgentControl,
pub(crate) network_proxy: ArcSwapOption<StartedNetworkProxy>,
pub(crate) credentialed_routes_reloader:
ArcSwapOption<codex_network_proxy::CredentialedRoutesReloader>,
pub(crate) network_proxy_audit_metadata: NetworkProxyAuditMetadata,
pub(crate) managed_network_requirements_configured: bool,
pub(crate) network_approval: Arc<NetworkApprovalService>,