Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ members = [
"utils/image",
"utils/json-to-toml",
"utils/home-dir",
"utils/process",
"utils/pty",
"utils/readiness",
"utils/redacted-string",
Expand Down Expand Up @@ -300,6 +301,7 @@ codex-utils-output-truncation = { path = "utils/output-truncation" }
codex-utils-path = { path = "utils/path-utils" }
codex-utils-path-uri = { path = "utils/path-uri" }
codex-utils-plugins = { path = "utils/plugins" }
codex-utils-process = { path = "utils/process" }
codex-utils-pty = { path = "utils/pty" }
codex-utils-redacted-string = { path = "utils/redacted-string" }
codex-utils-rustls-provider = { path = "utils/rustls-provider" }
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ doctest = false
workspace = true

[dependencies]
codex-utils-process = { workspace = true }
anyhow = { workspace = true }
base64 = { workspace = true }
axum = { workspace = true, default-features = false, features = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub(crate) async fn doctor_feedback_report(
}

fn doctor_command(executable: &Path, cwd: &Path, codex_home: &Path) -> Command {
let mut command = Command::new(executable);
let mut command = Command::from(codex_utils_process::background_command(executable));
command
.arg("--cd")
.arg(cwd)
Expand Down
10 changes: 9 additions & 1 deletion codex-rs/app-server/tests/suite/v2/model_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ requires_openai_auth = true
})
.await?;
let enabled = user_enablement.unwrap_or(app_enablement) && catalog_opt_in;
let expected = if enabled { &remote } else { &bundled };
let expected: &[ModelPreset] = if enabled {
&remote
} else if catalog_opt_in {
// The rollout gate suppresses discovery, but an explicit catalog still
// prevents bundled models from being advertised for this provider.
&[]
} else {
&bundled
};
assert_eq!(
response,
ModelListResponse {
Expand Down
1 change: 1 addition & 0 deletions codex-rs/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ doctest = false
workspace = true

[dependencies]
codex-utils-process = { workspace = true }
anyhow = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] }
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/cli/src/doctor/desktop/windows_security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async fn query_channel(wevtutil: &Path, channel: Channel) -> Option<Vec<Evidence
.map(|id| format!("EventID={id}"))
.collect::<Vec<_>>()
.join(" or ");
let mut child = Command::new(wevtutil)
let mut child = Command::from(codex_utils_process::background_command(wevtutil))
.args(["qe", channel.1])
.arg(format!(
"/q:*[System[({events}) and TimeCreated[timediff(@SystemTime) <= 604800000]]]"
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/cli/src/doctor/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ async fn endpoint_products() -> EndpointInspection {

#[cfg(any(target_os = "macos", target_os = "windows"))]
async fn product_command(program: impl AsRef<OsStr>, args: &[&str]) -> Option<Output> {
let mut command = Command::new(program);
let mut command = Command::from(codex_utils_process::background_command(program));
command
.args(args)
.stdin(Stdio::null())
Expand Down
13 changes: 11 additions & 2 deletions codex-rs/cli/tests/daemon_startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,17 @@ async fn restrictive_launcher_uses_embedded_if_daemon_cannot_start() -> Result<(
.arg("--no-alt-screen")
.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
let status = job.spawn_contained(&mut command)?.wait().await?;
.stderr(std::process::Stdio::inherit())
// This interactive CLI must inherit the PTY console rather than use
// the background helper's CREATE_NO_WINDOW launch policy.
.creation_flags(windows_sys::Win32::System::Threading::CREATE_SUSPENDED)
.kill_on_drop(true);
let mut child = command.spawn()?;
ensure!(
job.assign_and_resume_process(child.id().context("missing CLI pid")?)?,
"CLI job assignment failed"
);
let status = child.wait().await?;
ensure!(status.success(), "CLI exited: {status}");
return Ok(());
}
Expand Down
1 change: 1 addition & 0 deletions codex-rs/cli/tests/doctor_path_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ fn filesystem_probe_does_not_load_configuration() -> Result<()> {
#[tokio::test]
async fn interactive_tmux_startup_does_not_execute_workspace_helpers() -> Result<()> {
let fixture = Fixture::new()?;
std::fs::create_dir(fixture.workspace.join(".codex"))?;
let command = fixture.command()?;
let mut env: std::collections::HashMap<String, String> = std::env::vars().collect();
for (key, value) in command.get_envs() {
Expand Down
38 changes: 34 additions & 4 deletions codex-rs/cloud-tasks/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ pub fn append_error_log(message: impl AsRef<str>) {
/// - trims trailing '/'
/// - appends '/backend-api' for ChatGPT hosts when missing
pub fn normalize_base_url(input: &str) -> String {
let mut base_url = input.to_string();
while base_url.ends_with('/') {
base_url.pop();
}
let mut base_url = input.trim_end_matches('/').to_string();
if (base_url.starts_with("https://chatgpt.com")
|| base_url.starts_with("https://chat.openai.com"))
&& !base_url.contains("/backend-api")
Expand Down Expand Up @@ -171,3 +168,36 @@ pub fn format_relative_time(reference: DateTime<Utc>, ts: DateTime<Utc>) -> Stri
pub fn format_relative_time_now(ts: DateTime<Utc>) -> String {
format_relative_time(Utc::now(), ts)
}

#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

use super::normalize_base_url;

#[test]
fn normalize_base_url_normalizes_urls() {
for (input, expected) in [
("https://example.com/path", "https://example.com/path"),
("https://example.com/path/", "https://example.com/path"),
("https://example.com/path///", "https://example.com/path"),
("", ""),
("///", ""),
("https://chatgpt.com///", "https://chatgpt.com/backend-api"),
(
"https://chatgpt.com/backend-api///",
"https://chatgpt.com/backend-api",
),
(
"https://chat.openai.com///",
"https://chat.openai.com/backend-api",
),
(
"https://chat.openai.com/backend-api///",
"https://chat.openai.com/backend-api",
),
] {
assert_eq!(normalize_base_url(input), expected, "input: {input:?}");
}
}
}
3 changes: 2 additions & 1 deletion codex-rs/config/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,8 @@ pub async fn find_project_root(
.unwrap_or_else(|| cwd.clone()))
}

async fn discover_project_root(
/// Find a project marker, preserving absence instead of falling back to cwd.
pub async fn discover_project_root(
fs: &dyn ExecutorFileSystem,
cwd: &AbsolutePathBuf,
project_root_markers: &[String],
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core-plugins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ path = "src/lib.rs"
workspace = true

[dependencies]
codex-utils-process = { workspace = true }
anyhow = { workspace = true }
codex-analytics = { workspace = true }
codex-app-server-protocol = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core-plugins/src/git_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl PluginGitMode {
/// Creates a Git command with the repository-environment policy for this operation.
/// Automatic remote lookups and initial clones must also select a trusted repository.
pub(crate) fn command(self, git_binary: &Path) -> Command {
let mut command = Command::new(git_binary);
let mut command = codex_utils_process::background_command(git_binary);
command.args(["-c", codex_git_utils::SAFE_BARE_REPOSITORY_CONFIG]);
if matches!(self, Self::Automatic) {
command.env("GIT_OPTIONAL_LOCKS", "0");
Expand Down
3 changes: 1 addition & 2 deletions codex-rs/core-plugins/src/marketplace_add/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use super::MarketplaceAddError;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

pub(super) fn clone_git_source(
url: &str,
Expand Down Expand Up @@ -111,7 +110,7 @@ pub(super) fn marketplace_staging_root(install_root: &Path) -> PathBuf {
}

fn run_git(args: &[&str], cwd: Option<&Path>) -> Result<(), MarketplaceAddError> {
let mut command = Command::new("git");
let mut command = codex_utils_process::background_command("git");
command
.args(["-c", codex_git_utils::SAFE_BARE_REPOSITORY_CONFIG])
.args(args);
Expand Down
3 changes: 1 addition & 2 deletions codex-rs/core-plugins/src/npm_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;

const NPM_PLUGIN_SOURCE_STAGING_DIR: &str = "plugins/.marketplace-plugin-source-staging";
Expand Down Expand Up @@ -87,7 +86,7 @@ fn pack_npm_package(
|| package.to_string(),
|version| format!("{package}@{version}"),
);
let mut command = Command::new(npm_command);
let mut command = codex_utils_process::background_command(npm_command);
command
.current_dir(destination)
.arg("pack")
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ path = "src/lib.rs"
workspace = true

[dependencies]
codex-utils-process = { workspace = true }
anyhow = { workspace = true }
arc-swap = { workspace = true }
async-channel = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/compact_remote_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ async fn run_remote_compaction_request_v2(
err,
client_session,
sess,
turn_context,
step_context,
ResponsesStreamRequest::RemoteCompactionV2,
)
.await?;
Expand Down
23 changes: 22 additions & 1 deletion codex-rs/core/src/responses_retry.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//! Shared retry and transport fallback decisions for Responses requests.
//! Content-filter guidance is recorded for sampling requests before retry decisions.

use std::time::Duration;

use crate::client::ModelClientSession;
use crate::context::ContentFilterGuidance;
use crate::context::ContextualUserFragment;
use crate::session::session::Session;
use crate::session::step_context::StepContext;
use crate::session::turn_context::TurnContext;
use codex_client::RetryOperation;
use codex_features::Feature;
Expand Down Expand Up @@ -55,9 +59,26 @@ pub(crate) async fn handle_response_stream_error(
err: CodexErr,
client_session: &mut ModelClientSession,
sess: &Session,
turn_context: &TurnContext,
step_context: &StepContext,
request: ResponsesStreamRequest,
) -> Result<(), CodexErr> {
let turn_context = &step_context.turn;
if matches!(request, ResponsesStreamRequest::Sampling)
&& matches!(err.details(), CodexErrorDetails::ContentFilter)
{
let model_info = &step_context.settings.model_info;
let guidance = ContentFilterGuidance {
text: codex_prompts::ResolvedModelMessages::from_model(model_info)
.content_filter_guidance()
.to_string(),
};
sess.record_conversation_items(
turn_context,
model_info,
&[ContextualUserFragment::into(guidance)],
)
.await;
}
let operation = match request {
ResponsesStreamRequest::Sampling => RetryOperation::Sampling,
ResponsesStreamRequest::RemoteCompactionV2 => RetryOperation::RemoteCompactionV2,
Expand Down
Loading
Loading