Check app-server env on the server side

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Michael Fan
2026-03-27 14:50:08 -04:00
parent 2bf85d232a
commit efc0fa1880
17 changed files with 196 additions and 150 deletions

View File

@@ -58,8 +58,8 @@ use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::SkillsListParams;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadCompactStartParams;
use codex_app_server_protocol::ThreadDependencyEnvContainsParams;
use codex_app_server_protocol::ThreadDependencyEnvSetParams;
use codex_app_server_protocol::ThreadEnvContainsParams;
use codex_app_server_protocol::ThreadForkParams;
use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadLoadedListParams;
@@ -377,14 +377,13 @@ impl McpProcess {
self.send_request("thread/dependencyEnv/set", params).await
}
/// Send a `thread/dependencyEnv/contains` JSON-RPC request.
pub async fn send_thread_dependency_env_contains_request(
/// Send a `thread/env/contains` JSON-RPC request.
pub async fn send_thread_env_contains_request(
&mut self,
params: ThreadDependencyEnvContainsParams,
params: ThreadEnvContainsParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/dependencyEnv/contains", params)
.await
self.send_request("thread/env/contains", params).await
}
/// Send a `thread/metadata/update` JSON-RPC request.

View File

@@ -15,10 +15,10 @@ use codex_app_server_protocol::ItemStartedNotification;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ThreadDependencyEnvContainsParams;
use codex_app_server_protocol::ThreadDependencyEnvContainsResponse;
use codex_app_server_protocol::ThreadDependencyEnvSetParams;
use codex_app_server_protocol::ThreadDependencyEnvSetResponse;
use codex_app_server_protocol::ThreadEnvContainsParams;
use codex_app_server_protocol::ThreadEnvContainsResponse;
use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadReadParams;
use codex_app_server_protocol::ThreadReadResponse;
@@ -40,6 +40,61 @@ use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEPENDENCY_ENV_KEY: &str = "CODEX_TEST_DEPENDENCY_ENV_KEY";
const DEPENDENCY_ENV_VALUE: &str = "codex-test-dependency-env-value";
const PROCESS_ENV_KEY: &str = "CODEX_TEST_PROCESS_ENV_KEY";
const PROCESS_ENV_VALUE: &str = "codex-test-process-env-value";
#[tokio::test]
async fn thread_env_contains_checks_process_env() -> Result<()> {
let tmp = TempDir::new()?;
let codex_home = tmp.path().join("codex_home");
std::fs::create_dir(&codex_home)?;
let server = create_mock_responses_server_sequence(vec![]).await;
create_config_toml(
codex_home.as_path(),
&server.uri(),
"never",
&BTreeMap::default(),
)?;
let mut mcp = McpProcess::new_with_env(
codex_home.as_path(),
&[(PROCESS_ENV_KEY, Some(PROCESS_ENV_VALUE))],
)
.await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
persist_extended_history: true,
..Default::default()
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
let contains_id = mcp
.send_thread_env_contains_request(ThreadEnvContainsParams {
thread_id: thread.id,
key: PROCESS_ENV_KEY.to_string(),
})
.await?;
let contains_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(contains_id)),
)
.await??;
let contains = to_response::<ThreadEnvContainsResponse>(contains_resp)?;
assert_eq!(contains, ThreadEnvContainsResponse { contains: true });
Ok(())
}
#[tokio::test]
async fn thread_dependency_env_set_is_inherited_by_shell_commands() -> Result<()> {
@@ -72,9 +127,9 @@ async fn thread_dependency_env_set_is_inherited_by_shell_commands() -> Result<()
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
let contains_id = mcp
.send_thread_dependency_env_contains_request(ThreadDependencyEnvContainsParams {
.send_thread_env_contains_request(ThreadEnvContainsParams {
thread_id: thread.id.clone(),
key: "OPENAI_API_KEY".to_string(),
key: DEPENDENCY_ENV_KEY.to_string(),
})
.await?;
let contains_resp: JSONRPCResponse = timeout(
@@ -82,18 +137,15 @@ async fn thread_dependency_env_set_is_inherited_by_shell_commands() -> Result<()
mcp.read_stream_until_response_message(RequestId::Integer(contains_id)),
)
.await??;
let contains = to_response::<ThreadDependencyEnvContainsResponse>(contains_resp)?;
assert_eq!(
contains,
ThreadDependencyEnvContainsResponse { contains: false }
);
let contains = to_response::<ThreadEnvContainsResponse>(contains_resp)?;
assert_eq!(contains, ThreadEnvContainsResponse { contains: false });
let set_id = mcp
.send_thread_dependency_env_set_request(ThreadDependencyEnvSetParams {
thread_id: thread.id.clone(),
values: HashMap::from([(
"OPENAI_API_KEY".to_string(),
"sk-test-dependency-env".to_string(),
DEPENDENCY_ENV_KEY.to_string(),
DEPENDENCY_ENV_VALUE.to_string(),
)]),
})
.await?;
@@ -105,9 +157,9 @@ async fn thread_dependency_env_set_is_inherited_by_shell_commands() -> Result<()
let _: ThreadDependencyEnvSetResponse = to_response(set_resp)?;
let contains_id = mcp
.send_thread_dependency_env_contains_request(ThreadDependencyEnvContainsParams {
.send_thread_env_contains_request(ThreadEnvContainsParams {
thread_id: thread.id.clone(),
key: "OPENAI_API_KEY".to_string(),
key: DEPENDENCY_ENV_KEY.to_string(),
})
.await?;
let contains_resp: JSONRPCResponse = timeout(
@@ -115,16 +167,13 @@ async fn thread_dependency_env_set_is_inherited_by_shell_commands() -> Result<()
mcp.read_stream_until_response_message(RequestId::Integer(contains_id)),
)
.await??;
let contains = to_response::<ThreadDependencyEnvContainsResponse>(contains_resp)?;
assert_eq!(
contains,
ThreadDependencyEnvContainsResponse { contains: true }
);
let contains = to_response::<ThreadEnvContainsResponse>(contains_resp)?;
assert_eq!(contains, ThreadEnvContainsResponse { contains: true });
let shell_id = mcp
.send_thread_shell_command_request(ThreadShellCommandParams {
thread_id: thread.id,
command: "printf '%s\\n' \"$OPENAI_API_KEY\"".to_string(),
command: format!("printf '%s\\n' \"${DEPENDENCY_ENV_KEY}\""),
})
.await?;
let shell_resp: JSONRPCResponse = timeout(
@@ -145,7 +194,7 @@ async fn thread_dependency_env_set_is_inherited_by_shell_commands() -> Result<()
};
assert_eq!(
aggregated_output.as_deref(),
Some("sk-test-dependency-env\n")
Some(format!("{DEPENDENCY_ENV_VALUE}\n").as_str())
);
assert_eq!(*exit_code, Some(0));