mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Compare commits
2 Commits
shareable-
...
dh--add-gi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d22c2eef53 | ||
|
|
15cd23845c |
@@ -65,6 +65,7 @@ use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
#[cfg(test)]
|
||||
use crate::exec::StreamOutput;
|
||||
use crate::git_info::collect_git_info;
|
||||
// Removed: legacy executor wiring replaced by ToolOrchestrator flows.
|
||||
// legacy normalize_exec_result no longer used after orchestrator migration
|
||||
use crate::mcp::auth::compute_auth_statuses;
|
||||
@@ -127,6 +128,7 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::GitInfo;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
@@ -168,6 +170,8 @@ impl Codex {
|
||||
|
||||
let config = Arc::new(config);
|
||||
|
||||
let git_info = collect_git_info(&config.cwd).await;
|
||||
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
model: config.model.clone(),
|
||||
@@ -178,6 +182,7 @@ impl Codex {
|
||||
approval_policy: config.approval_policy,
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
cwd: config.cwd.clone(),
|
||||
git_info,
|
||||
original_config_do_not_use: Arc::clone(&config),
|
||||
};
|
||||
|
||||
@@ -263,6 +268,7 @@ pub(crate) struct TurnContext {
|
||||
/// the model as well as sandbox policies are resolved against this path
|
||||
/// instead of `std::env::current_dir()`.
|
||||
pub(crate) cwd: PathBuf,
|
||||
pub(crate) git_info: Option<GitInfo>,
|
||||
pub(crate) base_instructions: Option<String>,
|
||||
pub(crate) user_instructions: Option<String>,
|
||||
pub(crate) approval_policy: AskForApproval,
|
||||
@@ -313,6 +319,9 @@ pub(crate) struct SessionConfiguration {
|
||||
/// operate deterministically.
|
||||
cwd: PathBuf,
|
||||
|
||||
/// Git metadata for the current working directory, if available.
|
||||
git_info: Option<GitInfo>,
|
||||
|
||||
// TODO(pakrym): Remove config from here
|
||||
original_config_do_not_use: Arc<Config>,
|
||||
}
|
||||
@@ -338,6 +347,9 @@ impl SessionConfiguration {
|
||||
if let Some(cwd) = updates.cwd.clone() {
|
||||
next_configuration.cwd = cwd;
|
||||
}
|
||||
if let Some(git_info) = updates.git_info.clone() {
|
||||
next_configuration.git_info = git_info;
|
||||
}
|
||||
next_configuration
|
||||
}
|
||||
}
|
||||
@@ -345,6 +357,7 @@ impl SessionConfiguration {
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct SessionSettingsUpdate {
|
||||
pub(crate) cwd: Option<PathBuf>,
|
||||
pub(crate) git_info: Option<Option<GitInfo>>,
|
||||
pub(crate) approval_policy: Option<AskForApproval>,
|
||||
pub(crate) sandbox_policy: Option<SandboxPolicy>,
|
||||
pub(crate) model: Option<String>,
|
||||
@@ -398,6 +411,7 @@ impl Session {
|
||||
sub_id,
|
||||
client,
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
git_info: session_configuration.git_info.clone(),
|
||||
base_instructions: session_configuration.base_instructions.clone(),
|
||||
user_instructions: session_configuration.user_instructions.clone(),
|
||||
approval_policy: session_configuration.approval_policy,
|
||||
@@ -962,6 +976,7 @@ impl Session {
|
||||
Some(turn_context.approval_policy),
|
||||
Some(turn_context.sandbox_policy.clone()),
|
||||
Some(self.user_shell().clone()),
|
||||
turn_context.git_info.clone(),
|
||||
)));
|
||||
items
|
||||
}
|
||||
@@ -1190,7 +1205,7 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
|
||||
effort,
|
||||
summary,
|
||||
} => {
|
||||
let updates = SessionSettingsUpdate {
|
||||
let mut updates = SessionSettingsUpdate {
|
||||
cwd,
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
@@ -1199,11 +1214,14 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
|
||||
reasoning_summary: summary,
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(cwd) = updates.cwd.clone() {
|
||||
updates.git_info = Some(collect_git_info(&cwd).await);
|
||||
}
|
||||
sess.update_settings(updates).await;
|
||||
}
|
||||
|
||||
Op::UserInput { .. } | Op::UserTurn { .. } => {
|
||||
let (items, updates) = match sub.op {
|
||||
let (items, mut updates) = match sub.op {
|
||||
Op::UserTurn {
|
||||
cwd,
|
||||
approval_policy,
|
||||
@@ -1217,6 +1235,7 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
|
||||
items,
|
||||
SessionSettingsUpdate {
|
||||
cwd: Some(cwd),
|
||||
git_info: None,
|
||||
approval_policy: Some(approval_policy),
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
model: Some(model),
|
||||
@@ -1228,6 +1247,9 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
|
||||
Op::UserInput { items } => (items, SessionSettingsUpdate::default()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Some(cwd) = updates.cwd.clone() {
|
||||
updates.git_info = Some(collect_git_info(&cwd).await);
|
||||
}
|
||||
let current_context = sess.new_turn_with_sub_id(sub.id.clone(), updates).await;
|
||||
current_context
|
||||
.client
|
||||
@@ -1515,6 +1537,7 @@ async fn spawn_review_thread(
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
|
||||
cwd: parent_turn_context.cwd.clone(),
|
||||
git_info: parent_turn_context.git_info.clone(),
|
||||
is_review_mode: true,
|
||||
final_output_json_schema: None,
|
||||
codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(),
|
||||
@@ -2601,6 +2624,7 @@ mod tests {
|
||||
approval_policy: config.approval_policy,
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
cwd: config.cwd.clone(),
|
||||
git_info: None,
|
||||
original_config_do_not_use: Arc::clone(&config),
|
||||
};
|
||||
|
||||
@@ -2669,6 +2693,7 @@ mod tests {
|
||||
approval_policy: config.approval_policy,
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
cwd: config.cwd.clone(),
|
||||
git_info: None,
|
||||
original_config_do_not_use: Arc::clone(&config),
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::ENVIRONMENT_CONTEXT_CLOSE_TAG;
|
||||
use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG;
|
||||
use codex_protocol::protocol::GitInfo;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeriveDisplay)]
|
||||
@@ -29,6 +30,8 @@ pub(crate) struct EnvironmentContext {
|
||||
pub network_access: Option<NetworkAccess>,
|
||||
pub writable_roots: Option<Vec<PathBuf>>,
|
||||
pub shell: Option<Shell>,
|
||||
pub git_info: Option<GitInfo>,
|
||||
pub git_info_cleared: bool,
|
||||
}
|
||||
|
||||
impl EnvironmentContext {
|
||||
@@ -37,6 +40,7 @@ impl EnvironmentContext {
|
||||
approval_policy: Option<AskForApproval>,
|
||||
sandbox_policy: Option<SandboxPolicy>,
|
||||
shell: Option<Shell>,
|
||||
git_info: Option<GitInfo>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cwd,
|
||||
@@ -70,6 +74,8 @@ impl EnvironmentContext {
|
||||
_ => None,
|
||||
},
|
||||
shell,
|
||||
git_info,
|
||||
git_info_cleared: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +89,10 @@ impl EnvironmentContext {
|
||||
sandbox_mode,
|
||||
network_access,
|
||||
writable_roots,
|
||||
git_info,
|
||||
// should compare all fields except shell
|
||||
shell: _,
|
||||
git_info_cleared: _,
|
||||
} = other;
|
||||
|
||||
self.cwd == *cwd
|
||||
@@ -92,6 +100,7 @@ impl EnvironmentContext {
|
||||
&& self.sandbox_mode == *sandbox_mode
|
||||
&& self.network_access == *network_access
|
||||
&& self.writable_roots == *writable_roots
|
||||
&& self.git_info == *git_info
|
||||
}
|
||||
|
||||
pub fn diff(before: &TurnContext, after: &TurnContext) -> Self {
|
||||
@@ -110,7 +119,22 @@ impl EnvironmentContext {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
EnvironmentContext::new(cwd, approval_policy, sandbox_policy, None)
|
||||
let git_info_changed = before.git_info != after.git_info;
|
||||
let mut context = EnvironmentContext::new(
|
||||
cwd,
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
None,
|
||||
if git_info_changed {
|
||||
after.git_info.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
if git_info_changed && before.git_info.is_some() && after.git_info.is_none() {
|
||||
context.git_info_cleared = true;
|
||||
}
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +146,7 @@ impl From<&TurnContext> for EnvironmentContext {
|
||||
Some(turn_context.sandbox_policy.clone()),
|
||||
// Shell is not configurable from turn to turn
|
||||
None,
|
||||
turn_context.git_info.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -139,6 +164,7 @@ impl EnvironmentContext {
|
||||
/// <writable_roots>...</writable_roots>
|
||||
/// <network_access>...</network_access>
|
||||
/// <shell>...</shell>
|
||||
/// <git_info>...</git_info>
|
||||
/// </environment_context>
|
||||
/// ```
|
||||
pub fn serialize_to_xml(self) -> String {
|
||||
@@ -174,6 +200,23 @@ impl EnvironmentContext {
|
||||
{
|
||||
lines.push(format!(" <shell>{shell_name}</shell>"));
|
||||
}
|
||||
if let Some(git_info) = self.git_info {
|
||||
lines.push(" <git_info>".to_string());
|
||||
if let Some(commit_hash) = git_info.commit_hash {
|
||||
lines.push(format!(" <commit_hash>{commit_hash}</commit_hash>"));
|
||||
}
|
||||
if let Some(branch) = git_info.branch {
|
||||
lines.push(format!(" <branch>{branch}</branch>"));
|
||||
}
|
||||
if let Some(repository_url) = git_info.repository_url {
|
||||
lines.push(format!(
|
||||
" <repository_url>{repository_url}</repository_url>"
|
||||
));
|
||||
}
|
||||
lines.push(" </git_info>".to_string());
|
||||
} else if self.git_info_cleared {
|
||||
lines.push(" <git_info />".to_string());
|
||||
}
|
||||
lines.push(ENVIRONMENT_CONTEXT_CLOSE_TAG.to_string());
|
||||
lines.join("\n")
|
||||
}
|
||||
@@ -215,6 +258,7 @@ mod tests {
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo", "/tmp"], false)),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -238,6 +282,7 @@ mod tests {
|
||||
Some(AskForApproval::Never),
|
||||
Some(SandboxPolicy::ReadOnly),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -256,6 +301,7 @@ mod tests {
|
||||
Some(AskForApproval::OnFailure),
|
||||
Some(SandboxPolicy::DangerFullAccess),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -267,6 +313,48 @@ mod tests {
|
||||
assert_eq!(context.serialize_to_xml(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_environment_context_with_git_info() {
|
||||
let git_info = GitInfo {
|
||||
commit_hash: Some("abc123".to_string()),
|
||||
branch: Some("main".to_string()),
|
||||
repository_url: Some("https://example.com/repo.git".to_string()),
|
||||
};
|
||||
let context = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::ReadOnly),
|
||||
None,
|
||||
Some(git_info),
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
<cwd>/repo</cwd>
|
||||
<approval_policy>on-request</approval_policy>
|
||||
<sandbox_mode>read-only</sandbox_mode>
|
||||
<network_access>restricted</network_access>
|
||||
<git_info>
|
||||
<commit_hash>abc123</commit_hash>
|
||||
<branch>main</branch>
|
||||
<repository_url>https://example.com/repo.git</repository_url>
|
||||
</git_info>
|
||||
</environment_context>"#;
|
||||
|
||||
assert_eq!(context.serialize_to_xml(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_environment_context_with_cleared_git_info() {
|
||||
let mut context = EnvironmentContext::new(None, None, None, None, None);
|
||||
context.git_info_cleared = true;
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
<git_info />
|
||||
</environment_context>"#;
|
||||
|
||||
assert_eq!(context.serialize_to_xml(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equals_except_shell_compares_approval_policy() {
|
||||
// Approval policy
|
||||
@@ -275,12 +363,14 @@ mod tests {
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo"], false)),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::Never),
|
||||
Some(workspace_write_policy(vec!["/repo"], true)),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(!context1.equals_except_shell(&context2));
|
||||
}
|
||||
@@ -292,12 +382,14 @@ mod tests {
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::new_read_only_policy()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::new_workspace_write_policy()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!context1.equals_except_shell(&context2));
|
||||
@@ -310,12 +402,45 @@ mod tests {
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo", "/tmp", "/var"], false)),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo", "/tmp"], true)),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!context1.equals_except_shell(&context2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equals_except_shell_compares_git_info() {
|
||||
let git_info1 = Some(GitInfo {
|
||||
commit_hash: Some("abc".to_string()),
|
||||
branch: Some("main".to_string()),
|
||||
repository_url: None,
|
||||
});
|
||||
let git_info2 = Some(GitInfo {
|
||||
commit_hash: Some("def".to_string()),
|
||||
branch: Some("feature".to_string()),
|
||||
repository_url: None,
|
||||
});
|
||||
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo"], false)),
|
||||
None,
|
||||
git_info1,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo"], false)),
|
||||
None,
|
||||
git_info2,
|
||||
);
|
||||
|
||||
assert!(!context1.equals_except_shell(&context2));
|
||||
@@ -331,6 +456,7 @@ mod tests {
|
||||
shell_path: "/bin/bash".into(),
|
||||
bashrc_path: "/home/user/.bashrc".into(),
|
||||
})),
|
||||
None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
@@ -340,6 +466,7 @@ mod tests {
|
||||
shell_path: "/bin/zsh".into(),
|
||||
zshrc_path: "/home/user/.zshrc".into(),
|
||||
})),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(context1.equals_except_shell(&context2));
|
||||
|
||||
@@ -21,6 +21,7 @@ use core_test_support::load_sse_fixture_with_id;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::wait_for_event;
|
||||
use std::collections::HashMap;
|
||||
use std::process::Command as StdCommand;
|
||||
use tempfile::TempDir;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
@@ -883,3 +884,174 @@ async fn send_user_turn_with_changes_sends_environment_context() {
|
||||
]);
|
||||
assert_eq!(body2["input"], expected_input_2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn environment_context_includes_and_clears_git_info_on_cwd_change() {
|
||||
skip_if_no_network!();
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
|
||||
let sse = sse_completed("resp");
|
||||
let template = ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "text/event-stream")
|
||||
.set_body_raw(sse, "text/event-stream");
|
||||
|
||||
// Expect two POSTs to /v1/responses
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/v1/responses"))
|
||||
.respond_with(template)
|
||||
.expect(2)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let model_provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers()["openai"].clone()
|
||||
};
|
||||
|
||||
// Create a git repository for the initial cwd so that git_info is present
|
||||
let repo_dir = TempDir::new().unwrap();
|
||||
let repo_path = repo_dir.path();
|
||||
let envs = vec![
|
||||
("GIT_CONFIG_GLOBAL", "/dev/null"),
|
||||
("GIT_CONFIG_NOSYSTEM", "1"),
|
||||
];
|
||||
StdCommand::new("git")
|
||||
.envs(envs.clone())
|
||||
.args(["init"])
|
||||
.current_dir(repo_path)
|
||||
.output()
|
||||
.expect("git init should succeed");
|
||||
StdCommand::new("git")
|
||||
.envs(envs.clone())
|
||||
.args(["config", "user.name", "Test User"])
|
||||
.current_dir(repo_path)
|
||||
.output()
|
||||
.expect("git config user.name should succeed");
|
||||
StdCommand::new("git")
|
||||
.envs(envs.clone())
|
||||
.args(["config", "user.email", "test@example.com"])
|
||||
.current_dir(repo_path)
|
||||
.output()
|
||||
.expect("git config user.email should succeed");
|
||||
std::fs::write(repo_path.join("test.txt"), "content").unwrap();
|
||||
StdCommand::new("git")
|
||||
.envs(envs.clone())
|
||||
.args(["add", "."])
|
||||
.current_dir(repo_path)
|
||||
.output()
|
||||
.expect("git add should succeed");
|
||||
let commit_out = StdCommand::new("git")
|
||||
.envs(envs)
|
||||
.args(["commit", "-m", "initial"])
|
||||
.current_dir(repo_path)
|
||||
.output()
|
||||
.expect("git commit should succeed");
|
||||
assert!(commit_out.status.success(), "git commit failed");
|
||||
|
||||
// non-git directory for the override to ensure git_info is cleared
|
||||
let non_git_dir = TempDir::new().unwrap();
|
||||
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let mut config = load_default_config_for_test(&codex_home);
|
||||
config.cwd = repo_path.to_path_buf();
|
||||
config.model_provider = model_provider;
|
||||
config.user_instructions = Some("be consistent and helpful".to_string());
|
||||
|
||||
let conversation_manager =
|
||||
ConversationManager::with_auth(CodexAuth::from_api_key("Test API Key"));
|
||||
let codex = conversation_manager
|
||||
.new_conversation(config)
|
||||
.await
|
||||
.expect("create new conversation")
|
||||
.conversation;
|
||||
|
||||
// First turn in the git repo
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
|
||||
|
||||
// Override cwd to a non-git directory (this should clear git_info)
|
||||
codex
|
||||
.submit(Op::OverrideTurnContext {
|
||||
cwd: Some(non_git_dir.path().to_path_buf()),
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Second turn after cwd override
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
|
||||
|
||||
// Verify we issued exactly two requests
|
||||
let requests = server.received_requests().await.unwrap();
|
||||
assert_eq!(requests.len(), 2, "expected two POST requests");
|
||||
|
||||
let body1 = requests[0].body_json::<serde_json::Value>().unwrap();
|
||||
let body2 = requests[1].body_json::<serde_json::Value>().unwrap();
|
||||
|
||||
// prompt_cache_key should remain constant across cwd change
|
||||
assert_eq!(
|
||||
body1["prompt_cache_key"], body2["prompt_cache_key"],
|
||||
"prompt_cache_key should not change across cwd override"
|
||||
);
|
||||
|
||||
// Find the environment context message in the first request
|
||||
let input1 = body1["input"].as_array().expect("input must be an array");
|
||||
let env_text_1 = input1
|
||||
.iter()
|
||||
.filter_map(|it| it.get("content"))
|
||||
.filter_map(|c| c.as_array())
|
||||
.flat_map(|arr| arr.iter())
|
||||
.filter_map(|v| v.get("text"))
|
||||
.filter_map(|v| v.as_str())
|
||||
.find(|t| t.contains("<environment_context>"))
|
||||
.expect("first request should contain environment_context text");
|
||||
assert!(
|
||||
env_text_1.contains("<git_info>"),
|
||||
"initial environment_context should include <git_info>"
|
||||
);
|
||||
assert!(
|
||||
env_text_1.contains("<commit_hash>"),
|
||||
"git_info should include commit_hash"
|
||||
);
|
||||
assert!(
|
||||
env_text_1.contains("<branch>"),
|
||||
"git_info should include branch when on a branch"
|
||||
);
|
||||
|
||||
// The second request should include an environment_context update that clears git_info
|
||||
let input2 = body2["input"].as_array().expect("input must be an array");
|
||||
let env_text_2 = input2
|
||||
.iter()
|
||||
.filter_map(|it| it.get("content"))
|
||||
.filter_map(|c| c.as_array())
|
||||
.flat_map(|arr| arr.iter())
|
||||
.filter_map(|v| v.get("text"))
|
||||
.filter_map(|v| v.as_str())
|
||||
.rfind(|t| t.contains("<environment_context>"))
|
||||
.expect("second request should contain environment_context text");
|
||||
assert!(
|
||||
env_text_2.contains("<git_info />"),
|
||||
"environment_context after cwd override should include a cleared <git_info />"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1010,7 +1010,7 @@ pub struct RolloutLine {
|
||||
pub item: RolloutItem,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
|
||||
pub struct GitInfo {
|
||||
/// Current commit hash (SHA)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
Reference in New Issue
Block a user