mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Surface multi-environment choices in environment context (#20646)
## Why The model needs a way to see which environments are available during a multi-environment turn without changing the legacy single-environment prompt surface or pulling replay/persistence changes into the same review. ## Stack 1. https://github.com/openai/codex/pull/20646 - `EnvironmentContext` rendering for selected environments (this PR) 2. https://github.com/openai/codex/pull/20669 - selected-environment ownership and tool config prep 3. https://github.com/openai/codex/pull/20647 - process-tool `environment_id` routing ## What Changed - extend `environment_context` so multi-environment turns render an `<environments>` block with the selected environment ids and cwd values - keep zero- and single-environment turns on the existing cwd-only render path - keep replay and persistence paths on the legacy surface for now so this PR stays scoped to live prompt rendering - add focused coverage in `codex-rs/core/src/context/environment_context_tests.rs` ## Testing - CI --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
d55479488e
commit
2952beb009
@@ -1,6 +1,5 @@
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -74,8 +73,7 @@ async fn build_arc_monitor_request_includes_relevant_history_and_null_policies()
|
||||
.record_into_history(
|
||||
&[ContextualUserFragment::into(
|
||||
crate::context::EnvironmentContext::new(
|
||||
Some(PathBuf::from("/tmp")),
|
||||
"zsh".to_string(),
|
||||
Vec::new(),
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
|
||||
@@ -1,21 +1,85 @@
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::shell::Shell;
|
||||
use codex_protocol::protocol::TurnContextItem;
|
||||
use codex_protocol::protocol::TurnContextNetworkItem;
|
||||
use std::path::PathBuf;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use super::ContextualUserFragment;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct EnvironmentContext {
|
||||
pub(crate) cwd: Option<PathBuf>,
|
||||
pub(crate) shell: String,
|
||||
pub(crate) environments: EnvironmentContextEnvironments,
|
||||
pub(crate) current_date: Option<String>,
|
||||
pub(crate) timezone: Option<String>,
|
||||
pub(crate) network: Option<NetworkContext>,
|
||||
pub(crate) subagents: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct EnvironmentContextEnvironment {
|
||||
pub(crate) id: String,
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
pub(crate) shell: String,
|
||||
}
|
||||
|
||||
impl EnvironmentContextEnvironment {
|
||||
fn legacy(cwd: AbsolutePathBuf, shell: String) -> Self {
|
||||
Self {
|
||||
id: String::new(),
|
||||
cwd,
|
||||
shell,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_turn_environments(
|
||||
environments: &[crate::session::turn_context::TurnEnvironment],
|
||||
) -> Vec<Self> {
|
||||
environments
|
||||
.iter()
|
||||
.map(|environment| Self {
|
||||
id: environment.environment_id.clone(),
|
||||
cwd: environment.cwd.clone(),
|
||||
shell: environment.shell.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum EnvironmentContextEnvironments {
|
||||
None,
|
||||
Single(EnvironmentContextEnvironment),
|
||||
Multiple(Vec<EnvironmentContextEnvironment>),
|
||||
}
|
||||
|
||||
impl EnvironmentContextEnvironments {
|
||||
fn from_vec(environments: Vec<EnvironmentContextEnvironment>) -> Self {
|
||||
let mut environments = environments;
|
||||
match environments.pop() {
|
||||
None => Self::None,
|
||||
Some(environment) if environments.is_empty() => Self::Single(environment),
|
||||
Some(environment) => {
|
||||
environments.push(environment);
|
||||
Self::Multiple(environments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn equals_except_shell(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::None, Self::None) => true,
|
||||
(Self::Single(left), Self::Single(right)) => left.cwd == right.cwd,
|
||||
(Self::Multiple(left), Self::Multiple(right)) => {
|
||||
left.len() == right.len()
|
||||
&& left
|
||||
.iter()
|
||||
.zip(right.iter())
|
||||
.all(|(left, right)| left.id == right.id && left.cwd == right.cwd)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub(crate) struct NetworkContext {
|
||||
allowed_domains: Vec<String>,
|
||||
@@ -33,16 +97,30 @@ impl NetworkContext {
|
||||
|
||||
impl EnvironmentContext {
|
||||
pub(crate) fn new(
|
||||
cwd: Option<PathBuf>,
|
||||
shell: String,
|
||||
environments: Vec<EnvironmentContextEnvironment>,
|
||||
current_date: Option<String>,
|
||||
timezone: Option<String>,
|
||||
network: Option<NetworkContext>,
|
||||
subagents: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cwd,
|
||||
shell,
|
||||
environments: EnvironmentContextEnvironments::from_vec(environments),
|
||||
current_date,
|
||||
timezone,
|
||||
network,
|
||||
subagents,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_with_environments(
|
||||
environments: EnvironmentContextEnvironments,
|
||||
current_date: Option<String>,
|
||||
timezone: Option<String>,
|
||||
network: Option<NetworkContext>,
|
||||
subagents: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
environments,
|
||||
current_date,
|
||||
timezone,
|
||||
network,
|
||||
@@ -54,19 +132,11 @@ impl EnvironmentContext {
|
||||
/// comparing turn to turn, since the initial environment_context will
|
||||
/// include the shell, and then it is not configurable from turn to turn.
|
||||
pub(crate) fn equals_except_shell(&self, other: &EnvironmentContext) -> bool {
|
||||
let EnvironmentContext {
|
||||
cwd,
|
||||
current_date,
|
||||
timezone,
|
||||
network,
|
||||
subagents,
|
||||
shell: _,
|
||||
} = other;
|
||||
self.cwd == *cwd
|
||||
&& self.current_date == *current_date
|
||||
&& self.timezone == *timezone
|
||||
&& self.network == *network
|
||||
&& self.subagents == *subagents
|
||||
self.environments.equals_except_shell(&other.environments)
|
||||
&& self.current_date == other.current_date
|
||||
&& self.timezone == other.timezone
|
||||
&& self.network == other.network
|
||||
&& self.subagents == other.subagents
|
||||
}
|
||||
|
||||
pub(crate) fn diff_from_turn_context_item(
|
||||
@@ -74,18 +144,29 @@ impl EnvironmentContext {
|
||||
after: &EnvironmentContext,
|
||||
) -> Self {
|
||||
let before_network = Self::network_from_turn_context_item(before);
|
||||
let cwd = match &after.cwd {
|
||||
Some(cwd) if before.cwd.as_path() != cwd.as_path() => Some(cwd.clone()),
|
||||
_ => None,
|
||||
let environments = match &after.environments {
|
||||
EnvironmentContextEnvironments::Single(environment) => {
|
||||
if before.cwd.as_path() != environment.cwd.as_path() {
|
||||
EnvironmentContextEnvironments::Single(EnvironmentContextEnvironment::legacy(
|
||||
environment.cwd.clone(),
|
||||
environment.shell.clone(),
|
||||
))
|
||||
} else {
|
||||
EnvironmentContextEnvironments::None
|
||||
}
|
||||
}
|
||||
EnvironmentContextEnvironments::Multiple(environments) => {
|
||||
EnvironmentContextEnvironments::Multiple(environments.clone())
|
||||
}
|
||||
EnvironmentContextEnvironments::None => EnvironmentContextEnvironments::None,
|
||||
};
|
||||
let network = if before_network != after.network {
|
||||
after.network.clone()
|
||||
} else {
|
||||
before_network
|
||||
};
|
||||
EnvironmentContext::new(
|
||||
cwd,
|
||||
after.shell.clone(),
|
||||
EnvironmentContext::new_with_environments(
|
||||
environments,
|
||||
after.current_date.clone(),
|
||||
after.timezone.clone(),
|
||||
network,
|
||||
@@ -93,10 +174,9 @@ impl EnvironmentContext {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_turn_context(turn_context: &TurnContext, shell: &Shell) -> Self {
|
||||
pub(crate) fn from_turn_context(turn_context: &TurnContext) -> Self {
|
||||
Self::new(
|
||||
Some(turn_context.cwd.to_path_buf()),
|
||||
shell.name().to_string(),
|
||||
EnvironmentContextEnvironment::from_turn_environments(&turn_context.environments),
|
||||
turn_context.current_date.clone(),
|
||||
turn_context.timezone.clone(),
|
||||
Self::network_from_turn_context(turn_context),
|
||||
@@ -108,9 +188,12 @@ impl EnvironmentContext {
|
||||
turn_context_item: &TurnContextItem,
|
||||
shell: String,
|
||||
) -> Self {
|
||||
let cwd = match AbsolutePathBuf::try_from(turn_context_item.cwd.clone()) {
|
||||
Ok(cwd) => cwd,
|
||||
Err(_) => AbsolutePathBuf::resolve_path_against_base(&turn_context_item.cwd, "/"),
|
||||
};
|
||||
Self::new(
|
||||
Some(turn_context_item.cwd.clone()),
|
||||
shell,
|
||||
vec![EnvironmentContextEnvironment::legacy(cwd, shell)],
|
||||
turn_context_item.current_date.clone(),
|
||||
turn_context_item.timezone.clone(),
|
||||
Self::network_from_turn_context_item(turn_context_item),
|
||||
@@ -168,11 +251,29 @@ impl ContextualUserFragment for EnvironmentContext {
|
||||
|
||||
fn body(&self) -> String {
|
||||
let mut lines = Vec::new();
|
||||
if let Some(cwd) = &self.cwd {
|
||||
lines.push(format!(" <cwd>{}</cwd>", cwd.to_string_lossy()));
|
||||
match &self.environments {
|
||||
EnvironmentContextEnvironments::Single(environment) => {
|
||||
lines.push(format!(
|
||||
" <cwd>{}</cwd>",
|
||||
environment.cwd.to_string_lossy()
|
||||
));
|
||||
lines.push(format!(" <shell>{}</shell>", environment.shell));
|
||||
}
|
||||
EnvironmentContextEnvironments::Multiple(environments) => {
|
||||
lines.push(" <environments>".to_string());
|
||||
for environment in environments {
|
||||
lines.push(format!(" <environment id=\"{}\">", environment.id));
|
||||
lines.push(format!(
|
||||
" <cwd>{}</cwd>",
|
||||
environment.cwd.to_string_lossy()
|
||||
));
|
||||
lines.push(format!(" <shell>{}</shell>", environment.shell));
|
||||
lines.push(" </environment>".to_string());
|
||||
}
|
||||
lines.push(" </environments>".to_string());
|
||||
}
|
||||
EnvironmentContextEnvironments::None => {}
|
||||
}
|
||||
|
||||
lines.push(format!(" <shell>{}</shell>", self.shell));
|
||||
if let Some(current_date) = &self.current_date {
|
||||
lines.push(format!(" <current_date>{current_date}</current_date>"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::shell::ShellType;
|
||||
|
||||
use super::*;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use core_test_support::test_path_buf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
@@ -14,12 +15,19 @@ fn fake_shell_name() -> String {
|
||||
shell.name().to_string()
|
||||
}
|
||||
|
||||
fn test_abs_path(unix_path: &str) -> AbsolutePathBuf {
|
||||
test_path_buf(unix_path).abs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_workspace_write_environment_context() {
|
||||
let cwd = test_path_buf("/repo");
|
||||
let context = EnvironmentContext::new(
|
||||
Some(cwd.clone()),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: cwd.abs(),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
/*network*/ None,
|
||||
@@ -46,8 +54,11 @@ fn serialize_environment_context_with_network() {
|
||||
vec!["blocked.example.com".to_string()],
|
||||
);
|
||||
let context = EnvironmentContext::new(
|
||||
Some(test_path_buf("/repo")),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_path_buf("/repo").abs(),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
Some(network),
|
||||
@@ -75,8 +86,7 @@ fn serialize_environment_context_with_network() {
|
||||
#[test]
|
||||
fn serialize_read_only_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
/*cwd*/ None,
|
||||
fake_shell_name(),
|
||||
Vec::new(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
/*network*/ None,
|
||||
@@ -84,7 +94,6 @@ fn serialize_read_only_environment_context() {
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
<shell>bash</shell>
|
||||
<current_date>2026-02-26</current_date>
|
||||
<timezone>America/Los_Angeles</timezone>
|
||||
</environment_context>"#;
|
||||
@@ -95,16 +104,22 @@ fn serialize_read_only_environment_context() {
|
||||
#[test]
|
||||
fn equals_except_shell_compares_cwd() {
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_abs_path("/repo"),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_abs_path("/repo"),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
@@ -116,16 +131,22 @@ fn equals_except_shell_compares_cwd() {
|
||||
#[test]
|
||||
fn equals_except_shell_compares_cwd_differences() {
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo1")),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_abs_path("/repo1"),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo2")),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_abs_path("/repo2"),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
@@ -138,16 +159,22 @@ fn equals_except_shell_compares_cwd_differences() {
|
||||
#[test]
|
||||
fn equals_except_shell_ignores_shell() {
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
"bash".to_string(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_abs_path("/repo"),
|
||||
shell: "bash".to_string(),
|
||||
}],
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
"zsh".to_string(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "other".to_string(),
|
||||
cwd: test_abs_path("/repo"),
|
||||
shell: "zsh".to_string(),
|
||||
}],
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
@@ -160,8 +187,11 @@ fn equals_except_shell_ignores_shell() {
|
||||
#[test]
|
||||
fn serialize_environment_context_with_subagents() {
|
||||
let context = EnvironmentContext::new(
|
||||
Some(test_path_buf("/repo")),
|
||||
fake_shell_name(),
|
||||
vec![EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: test_path_buf("/repo").abs(),
|
||||
shell: fake_shell_name(),
|
||||
}],
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
/*network*/ None,
|
||||
@@ -184,3 +214,48 @@ fn serialize_environment_context_with_subagents() {
|
||||
|
||||
assert_eq!(context.render(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_environment_context_with_multiple_selected_environments() {
|
||||
let local_cwd = test_path_buf("/repo/local");
|
||||
let remote_cwd = test_path_buf("/repo/remote");
|
||||
let context = EnvironmentContext::new(
|
||||
vec![
|
||||
EnvironmentContextEnvironment {
|
||||
id: "local".to_string(),
|
||||
cwd: local_cwd.abs(),
|
||||
shell: "bash".to_string(),
|
||||
},
|
||||
EnvironmentContextEnvironment {
|
||||
id: "remote".to_string(),
|
||||
cwd: remote_cwd.abs(),
|
||||
shell: "bash".to_string(),
|
||||
},
|
||||
],
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = format!(
|
||||
r#"<environment_context>
|
||||
<environments>
|
||||
<environment id="local">
|
||||
<cwd>{}</cwd>
|
||||
<shell>bash</shell>
|
||||
</environment>
|
||||
<environment id="remote">
|
||||
<cwd>{}</cwd>
|
||||
<shell>bash</shell>
|
||||
</environment>
|
||||
</environments>
|
||||
<current_date>2026-02-26</current_date>
|
||||
<timezone>America/Los_Angeles</timezone>
|
||||
</environment_context>"#,
|
||||
local_cwd.display(),
|
||||
remote_cwd.display()
|
||||
);
|
||||
|
||||
assert_eq!(context.render(), expected);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ fn build_environment_update_item(
|
||||
|
||||
let prev = previous?;
|
||||
let prev_context = EnvironmentContext::from_turn_context_item(prev, shell.name().to_string());
|
||||
let next_context = EnvironmentContext::from_turn_context(next, shell);
|
||||
let next_context = EnvironmentContext::from_turn_context(next);
|
||||
if prev_context.equals_except_shell(&next_context) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ pub(crate) fn resolve_environment_selections(
|
||||
environment_id,
|
||||
environment,
|
||||
cwd: selected_environment.cwd.clone(),
|
||||
// TODO(starr): Resolve shell metadata per environment instead of
|
||||
// hardcoding bash.
|
||||
shell: "bash".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2534,7 +2534,6 @@ impl Session {
|
||||
) -> Vec<ResponseItem> {
|
||||
let mut developer_sections = Vec::<String>::with_capacity(8);
|
||||
let mut contextual_user_sections = Vec::<String>::with_capacity(2);
|
||||
let shell = self.user_shell();
|
||||
let (
|
||||
reference_context_item,
|
||||
previous_turn_settings,
|
||||
@@ -2695,7 +2694,7 @@ impl Session {
|
||||
.format_environment_context_subagents(self.conversation_id)
|
||||
.await;
|
||||
contextual_user_sections.push(
|
||||
crate::context::EnvironmentContext::from_turn_context(turn_context, shell.as_ref())
|
||||
crate::context::EnvironmentContext::from_turn_context(turn_context)
|
||||
.with_subagents(subagents)
|
||||
.render(),
|
||||
);
|
||||
|
||||
@@ -2949,6 +2949,7 @@ fn turn_environments_for_tests(
|
||||
environment_id: codex_exec_server::LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
environment: Arc::clone(environment),
|
||||
cwd: cwd.clone(),
|
||||
shell: "bash".to_string(),
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -4488,6 +4489,7 @@ async fn primary_environment_uses_first_turn_environment() {
|
||||
environment_id: "second".to_string(),
|
||||
environment: Arc::clone(&first_environment.environment),
|
||||
cwd: second_cwd.clone(),
|
||||
shell: first_environment.shell.clone(),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -34,6 +34,7 @@ pub(crate) struct TurnEnvironment {
|
||||
pub(crate) environment_id: String,
|
||||
pub(crate) environment: Arc<Environment>,
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
pub(crate) shell: String,
|
||||
}
|
||||
|
||||
impl TurnEnvironment {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use codex_apply_patch::APPLY_PATCH_TOOL_INSTRUCTIONS;
|
||||
use codex_core::shell::Shell;
|
||||
use codex_core::shell::default_user_shell;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
@@ -46,8 +44,7 @@ fn text_user_input_parts(texts: Vec<String>) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_default_env_context(text: &str, cwd: &str, shell: &Shell) {
|
||||
let shell_name = shell.name();
|
||||
fn assert_default_env_context(text: &str, cwd: &str) {
|
||||
assert!(
|
||||
text.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG),
|
||||
"expected environment context fragment: {text}"
|
||||
@@ -57,7 +54,7 @@ fn assert_default_env_context(text: &str, cwd: &str, shell: &Shell) {
|
||||
"expected cwd in environment context: {text}"
|
||||
);
|
||||
assert!(
|
||||
text.contains(&format!("<shell>{shell_name}</shell>")),
|
||||
text.contains("<shell>bash</shell>"),
|
||||
"expected shell in environment context: {text}"
|
||||
);
|
||||
assert!(
|
||||
@@ -365,12 +362,11 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
|
||||
"expected user instructions in UI message: {ui_text}"
|
||||
);
|
||||
|
||||
let shell = default_user_shell();
|
||||
let cwd_str = config.cwd.to_string_lossy();
|
||||
let env_text = input1[1]["content"][1]["text"]
|
||||
.as_str()
|
||||
.expect("environment context text");
|
||||
assert_default_env_context(env_text, &cwd_str, &shell);
|
||||
assert_default_env_context(env_text, &cwd_str);
|
||||
assert_eq!(
|
||||
input1[1]["content"][1]["type"].as_str(),
|
||||
Some("input_text"),
|
||||
@@ -785,9 +781,8 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
||||
let env_text = expected_env_msg_2["content"][0]["text"]
|
||||
.as_str()
|
||||
.expect("environment context text");
|
||||
let shell = default_user_shell();
|
||||
let expected_cwd = new_cwd.path().display().to_string();
|
||||
assert_default_env_context(env_text, &expected_cwd, &shell);
|
||||
assert_default_env_context(env_text, &expected_cwd);
|
||||
let mut expected_body2 = body1_input.to_vec();
|
||||
expected_body2.push(expected_settings_update_msg);
|
||||
expected_body2.push(expected_env_msg_2);
|
||||
@@ -891,13 +886,12 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
let expected_permissions_msg = body1["input"][0].clone();
|
||||
let expected_ui_msg = body1["input"][1].clone();
|
||||
|
||||
let shell = default_user_shell();
|
||||
let default_cwd_lossy = default_cwd.to_string_lossy();
|
||||
let expected_env_text_1 = expected_ui_msg["content"][1]["text"]
|
||||
.as_str()
|
||||
.expect("cached environment context text")
|
||||
.to_string();
|
||||
assert_default_env_context(&expected_env_text_1, &default_cwd_lossy, &shell);
|
||||
assert_default_env_context(&expected_env_text_1, &default_cwd_lossy);
|
||||
|
||||
let expected_contextual_user_msg_1 = text_user_input_parts(vec![
|
||||
expected_ui_msg["content"][0]["text"]
|
||||
@@ -1023,12 +1017,11 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
let expected_permissions_msg = body1["input"][0].clone();
|
||||
let expected_ui_msg = body1["input"][1].clone();
|
||||
|
||||
let shell = default_user_shell();
|
||||
let expected_env_text_1 = expected_ui_msg["content"][1]["text"]
|
||||
.as_str()
|
||||
.expect("cached environment context text")
|
||||
.to_string();
|
||||
assert_default_env_context(&expected_env_text_1, &default_cwd.to_string_lossy(), &shell);
|
||||
assert_default_env_context(&expected_env_text_1, &default_cwd.to_string_lossy());
|
||||
let expected_contextual_user_msg_1 = text_user_input_parts(vec![
|
||||
expected_ui_msg["content"][0]["text"]
|
||||
.as_str()
|
||||
|
||||
Reference in New Issue
Block a user