mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
Tell model about credentialed routes
This commit is contained in:
Generated
+1
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,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}"))
|
||||
})
|
||||
|
||||
@@ -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]"));
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user