mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
app-server: accept environment-native cwd
This commit is contained in:
+9
-1
@@ -78,6 +78,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -4331,7 +4334,12 @@
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "Absolute cwd rendered using the selected environment's path convention."
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
|
||||
+6
-1
@@ -19517,7 +19517,12 @@
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "Absolute cwd rendered using the selected environment's path convention."
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
|
||||
+6
-1
@@ -17325,7 +17325,12 @@
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "Absolute cwd rendered using the selected environment's path convention."
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -242,7 +245,12 @@
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "Absolute cwd rendered using the selected environment's path convention."
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -331,7 +334,12 @@
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "Absolute cwd rendered using the selected environment's path convention."
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
|
||||
+6
-2
@@ -1,6 +1,10 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { ApiPathString } from "../ApiPathString";
|
||||
|
||||
export type TurnEnvironmentParams = { environmentId: string, cwd: AbsolutePathBuf, };
|
||||
export type TurnEnvironmentParams = { environmentId: string,
|
||||
/**
|
||||
* Absolute cwd rendered using the selected environment's path convention.
|
||||
*/
|
||||
cwd: ApiPathString, };
|
||||
|
||||
@@ -3723,7 +3723,7 @@ fn thread_settings_update_params_preserve_field_level_experimental_gates() {
|
||||
|
||||
#[test]
|
||||
fn turn_start_params_round_trip_environments() {
|
||||
let cwd = test_absolute_path();
|
||||
let cwd = ApiPathString::new(r"C:\workspace");
|
||||
let params: TurnStartParams = serde_json::from_value(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
@@ -3806,8 +3806,8 @@ fn turn_start_params_treat_null_or_omitted_environments_as_default() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_start_params_reject_relative_environment_cwd() {
|
||||
let err = serde_json::from_value::<TurnStartParams>(json!({
|
||||
fn turn_start_params_preserve_relative_environment_cwd_for_boundary_validation() {
|
||||
let params = serde_json::from_value::<TurnStartParams>(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
"environments": [
|
||||
@@ -3817,12 +3817,14 @@ fn turn_start_params_reject_relative_environment_cwd() {
|
||||
}
|
||||
],
|
||||
}))
|
||||
.expect_err("relative environment cwd should fail");
|
||||
.expect("native cwd should deserialize without applying host rules");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("AbsolutePathBuf deserialized without a base path"),
|
||||
"unexpected error: {err}"
|
||||
assert_eq!(
|
||||
params.environments,
|
||||
Some(vec![TurnEnvironmentParams {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: ApiPathString::new("relative"),
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use codex_protocol::user_input::ByteRange as CoreByteRange;
|
||||
use codex_protocol::user_input::TextElement as CoreTextElement;
|
||||
use codex_protocol::user_input::UserInput as CoreUserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -38,7 +39,8 @@ pub enum TurnStatus {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnEnvironmentParams {
|
||||
pub environment_id: String,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
/// Absolute cwd rendered using the selected environment's path convention.
|
||||
pub cwd: ApiPathString,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
@@ -709,7 +709,7 @@ Turns attach user input (text or images) to a thread and trigger Codex generatio
|
||||
- `{"type":"image","url":"https://…png"}`
|
||||
- `{"type":"localImage","path":"/tmp/screenshot.png"}`
|
||||
|
||||
You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread. `outputSchema` applies only to the current turn. Experimental `environments` is turn-scoped: omit it to inherit the thread's sticky environments, pass `[]` to run the turn with no environments, or pass explicit environment ids to override the sticky selection for this turn only.
|
||||
You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread. `outputSchema` applies only to the current turn. Experimental `environments` is turn-scoped: omit it to inherit the thread's sticky environments, pass `[]` to run the turn with no environments, or pass explicit environment ids to override the sticky selection for this turn only. Each environment `cwd` must be absolute and use that environment's native path convention, so a Windows environment uses a value such as `C:\\workspace` even when app-server runs on Linux.
|
||||
|
||||
`approvalsReviewer` accepts:
|
||||
|
||||
|
||||
@@ -541,6 +541,24 @@ fn resolve_request_cwd(cwd: Option<PathBuf>) -> Result<Option<AbsolutePathBuf>,
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn resolve_turn_environment_selections(
|
||||
thread_manager: &ThreadManager,
|
||||
environments: Option<Vec<TurnEnvironmentParams>>,
|
||||
) -> Result<Option<Vec<TurnEnvironmentSelection>>, JSONRPCErrorError> {
|
||||
let Some(environments) = environments else {
|
||||
return Ok(None);
|
||||
};
|
||||
thread_manager
|
||||
.resolve_native_environment_selections(
|
||||
environments
|
||||
.into_iter()
|
||||
.map(|environment| (environment.environment_id, environment.cwd)),
|
||||
)
|
||||
.await
|
||||
.map(Some)
|
||||
.map_err(environment_selection_error)
|
||||
}
|
||||
|
||||
fn resolve_runtime_workspace_roots(workspace_roots: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf> {
|
||||
let mut resolved_roots = Vec::new();
|
||||
for root in workspace_roots {
|
||||
|
||||
@@ -906,7 +906,8 @@ impl ThreadRequestProcessor {
|
||||
"`permissions` cannot be combined with `sandbox`",
|
||||
));
|
||||
}
|
||||
let environment_selections = self.parse_environment_selections(environments)?;
|
||||
let environment_selections =
|
||||
resolve_turn_environment_selections(self.thread_manager.as_ref(), environments).await?;
|
||||
let runtime_workspace_roots = runtime_workspace_roots.map(resolve_runtime_workspace_roots);
|
||||
let mut typesafe_overrides = self.build_thread_config_overrides(
|
||||
model,
|
||||
@@ -1311,27 +1312,6 @@ impl ThreadRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_environment_selections(
|
||||
&self,
|
||||
environments: Option<Vec<TurnEnvironmentParams>>,
|
||||
) -> Result<Option<Vec<TurnEnvironmentSelection>>, JSONRPCErrorError> {
|
||||
let environment_selections = environments.map(|environments| {
|
||||
environments
|
||||
.into_iter()
|
||||
.map(|environment| TurnEnvironmentSelection {
|
||||
environment_id: environment.environment_id,
|
||||
cwd: PathUri::from_abs_path(&environment.cwd),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
if let Some(environment_selections) = environment_selections.as_ref() {
|
||||
self.thread_manager
|
||||
.validate_environment_selections(environment_selections)
|
||||
.map_err(environment_selection_error)?;
|
||||
}
|
||||
Ok(environment_selections)
|
||||
}
|
||||
|
||||
async fn thread_archive_inner(
|
||||
&self,
|
||||
params: ThreadArchiveParams,
|
||||
|
||||
@@ -341,27 +341,6 @@ impl TurnRequestProcessor {
|
||||
Ok((review_request, hint))
|
||||
}
|
||||
|
||||
fn parse_environment_selections(
|
||||
&self,
|
||||
environments: Option<Vec<TurnEnvironmentParams>>,
|
||||
) -> Result<Option<Vec<TurnEnvironmentSelection>>, JSONRPCErrorError> {
|
||||
let environment_selections = environments.map(|environments| {
|
||||
environments
|
||||
.into_iter()
|
||||
.map(|environment| TurnEnvironmentSelection {
|
||||
environment_id: environment.environment_id,
|
||||
cwd: PathUri::from_abs_path(&environment.cwd),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
if let Some(environment_selections) = environment_selections.as_ref() {
|
||||
self.thread_manager
|
||||
.validate_environment_selections(environment_selections)
|
||||
.map_err(environment_selection_error)?;
|
||||
}
|
||||
Ok(environment_selections)
|
||||
}
|
||||
|
||||
async fn request_trace_context(
|
||||
&self,
|
||||
request_id: &ConnectionRequestId,
|
||||
@@ -433,7 +412,9 @@ impl TurnRequestProcessor {
|
||||
self.track_error_response(&request_id, error, /*error_type*/ None);
|
||||
})?;
|
||||
|
||||
let environment_selections = self.parse_environment_selections(params.environments)?;
|
||||
let environment_selections =
|
||||
resolve_turn_environment_selections(self.thread_manager.as_ref(), params.environments)
|
||||
.await?;
|
||||
|
||||
// Map v2 input items to core input items.
|
||||
let mapped_items: Vec<CoreInputItem> = params
|
||||
|
||||
@@ -33,6 +33,7 @@ use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
@@ -293,7 +294,7 @@ async fn thread_start_rejects_unknown_environment_as_invalid_request() -> Result
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
environments: Some(vec![TurnEnvironmentParams {
|
||||
environment_id: "missing".to_string(),
|
||||
cwd: codex_home.path().to_path_buf().try_into()?,
|
||||
cwd: ApiPathString::new(codex_home.path().to_string_lossy()),
|
||||
}]),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
@@ -76,6 +76,7 @@ use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
||||
use codex_protocol::models::ImageDetail;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -1326,7 +1327,7 @@ async fn turn_start_rejects_unknown_environment_before_starting_turn() -> Result
|
||||
}],
|
||||
environments: Some(vec![TurnEnvironmentParams {
|
||||
environment_id: "missing".to_string(),
|
||||
cwd: codex_home.path().to_path_buf().try_into()?,
|
||||
cwd: ApiPathString::new(codex_home.path().to_string_lossy()),
|
||||
}]),
|
||||
..Default::default()
|
||||
})
|
||||
@@ -2744,7 +2745,7 @@ fn environment_params(
|
||||
.map(|id| {
|
||||
Ok(TurnEnvironmentParams {
|
||||
environment_id: (*id).to_string(),
|
||||
cwd: cwd.to_path_buf().try_into()?,
|
||||
cwd: ApiPathString::new(cwd.to_string_lossy()),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -66,6 +66,7 @@ use codex_thread_store::ThreadStore;
|
||||
use codex_thread_store::ThreadStoreError;
|
||||
use codex_thread_store::UpdateThreadMetadataParams;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use std::collections::HashMap;
|
||||
@@ -472,6 +473,52 @@ impl ThreadManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolves environment-native cwd strings using metadata from each selected environment.
|
||||
pub async fn resolve_native_environment_selections(
|
||||
&self,
|
||||
environments: impl IntoIterator<Item = (String, ApiPathString)>,
|
||||
) -> CodexResult<Vec<TurnEnvironmentSelection>> {
|
||||
let environments = environments.into_iter();
|
||||
let mut environment_ids = HashSet::new();
|
||||
let mut selections = Vec::with_capacity(environments.size_hint().0);
|
||||
for (environment_id, cwd) in environments {
|
||||
if !environment_ids.insert(environment_id.clone()) {
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"duplicate turn environment id `{environment_id}`"
|
||||
)));
|
||||
}
|
||||
let environment = self
|
||||
.state
|
||||
.environment_manager
|
||||
.get_environment(&environment_id)
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{environment_id}`"
|
||||
))
|
||||
})?;
|
||||
let info = environment.info().await.map_err(|err| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"failed to get info for environment `{environment_id}`: {err}"
|
||||
))
|
||||
})?;
|
||||
crate::shell::Shell::from_environment_shell_info(info.shell).map_err(|err| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"failed to resolve shell for environment `{environment_id}`: {err}"
|
||||
))
|
||||
})?;
|
||||
let cwd = cwd.to_path_uri(info.path_convention).map_err(|err| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"invalid cwd for environment `{environment_id}`: {err}"
|
||||
))
|
||||
})?;
|
||||
selections.push(TurnEnvironmentSelection {
|
||||
environment_id,
|
||||
cwd,
|
||||
});
|
||||
}
|
||||
Ok(selections)
|
||||
}
|
||||
|
||||
pub fn get_models_manager(&self) -> SharedModelsManager {
|
||||
self.state.models_manager.clone()
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
use codex_protocol::protocol::TurnStartedEvent;
|
||||
use codex_protocol::protocol::UserMessageEvent;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use core_test_support::PathBufExt;
|
||||
use core_test_support::PathExt;
|
||||
@@ -298,6 +300,131 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
|
||||
assert!(manager.list_thread_ids().await.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_thread_rejects_explicit_local_environment_when_default_provider_is_disabled() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home").abs();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let runtime_paths = codex_exec_server::ExecServerRuntimePaths::new(
|
||||
std::env::current_exe().expect("current exe path"),
|
||||
/*codex_linux_sandbox_exe*/ None,
|
||||
)
|
||||
.expect("runtime paths");
|
||||
let environment_manager = Arc::new(
|
||||
codex_exec_server::EnvironmentManager::create_for_tests(
|
||||
Some("none".to_string()),
|
||||
Some(runtime_paths),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
let manager = ThreadManager::with_models_provider_and_home_for_tests(
|
||||
CodexAuth::from_api_key("dummy"),
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
environment_manager,
|
||||
);
|
||||
|
||||
let result = manager
|
||||
.start_thread_with_options(StartThreadOptions {
|
||||
config: config.clone(),
|
||||
initial_history: InitialHistory::New,
|
||||
session_source: None,
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
parent_trace: None,
|
||||
environments: vec![TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: PathUri::from_abs_path(&config.cwd),
|
||||
}],
|
||||
thread_extension_init: Default::default(),
|
||||
})
|
||||
.await;
|
||||
let err = match result {
|
||||
Ok(_) => panic!("explicit local environment should not resolve when provider is disabled"),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
assert_eq!(err.to_string(), "unknown turn environment id `local`");
|
||||
assert!(manager.list_thread_ids().await.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn native_environment_selections_use_selected_environment_metadata() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home").abs();
|
||||
config.cwd = temp_dir.path().join("workspace").abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
std::fs::create_dir_all(&config.cwd).expect("create workspace");
|
||||
|
||||
let manager = ThreadManager::with_models_provider_and_home_for_tests(
|
||||
CodexAuth::from_api_key("dummy"),
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
);
|
||||
let selections = manager
|
||||
.resolve_native_environment_selections([(
|
||||
"local".to_string(),
|
||||
ApiPathString::new(config.cwd.display().to_string()),
|
||||
)])
|
||||
.await
|
||||
.expect("resolve local selection");
|
||||
|
||||
assert_eq!(
|
||||
selections,
|
||||
vec![TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: PathUri::from_abs_path(&config.cwd),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn native_environment_selections_reject_duplicate_ids() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home").abs();
|
||||
config.cwd = temp_dir.path().join("workspace").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("dummy"),
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
);
|
||||
let native_cwd = ApiPathString::new(config.cwd.display().to_string());
|
||||
let error = manager
|
||||
.resolve_native_environment_selections([
|
||||
("local".to_string(), native_cwd.clone()),
|
||||
("local".to_string(), native_cwd),
|
||||
])
|
||||
.await
|
||||
.expect_err("duplicate ids must fail");
|
||||
|
||||
assert_eq!(error.to_string(), "duplicate turn environment id `local`");
|
||||
|
||||
let error = manager
|
||||
.resolve_native_environment_selections([(
|
||||
"local".to_string(),
|
||||
ApiPathString::new("relative"),
|
||||
)])
|
||||
.await
|
||||
.expect_err("relative cwd must fail at the environment boundary");
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"invalid cwd for environment `local`: path `relative` is not absolute using {} path syntax",
|
||||
PathConvention::native()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_thread_keeps_internal_threads_hidden_from_normal_lookups() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user