Files
codex/codex-rs/core/src/util.rs
Eric Traut 3dc278b68e Trim TUI legacy core helper usage (#22695)
## Why

The TUI still had a few low-risk dependencies flowing through the
transitional `legacy_core` namespace after the app-server migration.
These helpers either already have clearer non-core owners or are
presentation logic that does not belong in `codex-core`, so moving them
out reduces the compatibility surface without changing product behavior.

## What changed

This is a low-risk change, almost completely mechanical in nature.

- Route TUI Codex-home lookup through `codex-utils-home-dir`, use
`Config::log_dir` directly, and call
`codex-sandboxing::system_bwrap_warning` without going through
`legacy_core`.
- Move shared `codex resume` hint formatting from `codex-core` into
`codex-utils-cli`.
- Update CLI and TUI call sites to use the shared CLI utility, and keep
the resume-command behavior covered by tests in its new home.

## Verification

- `cargo test -p codex-utils-cli`
- `cargo test -p codex-utils-cli resume_command`
2026-05-14 16:54:59 -07:00

121 lines
3.3 KiB
Rust

use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;
use rand::Rng;
use tracing::error;
const INITIAL_DELAY_MS: u64 = 200;
const BACKOFF_FACTOR: f64 = 2.0;
/// Emit structured feedback metadata as key/value pairs.
///
/// This logs a tracing event with `target: "feedback_tags"`. If
/// `codex_feedback::CodexFeedback::metadata_layer()` is installed, these fields are captured and
/// later attached as tags when feedback is uploaded.
///
/// Values are wrapped with [`tracing::field::DebugValue`], so the expression only needs to
/// implement [`std::fmt::Debug`].
///
/// Example:
///
/// ```rust
/// codex_core::feedback_tags!(model = "gpt-5", cached = true);
/// codex_core::feedback_tags!(provider = provider_id, request_id = request_id);
/// ```
#[macro_export]
macro_rules! feedback_tags {
($( $key:ident = $value:expr ),+ $(,)?) => {
::tracing::info!(
target: "feedback_tags",
$( $key = ::tracing::field::debug(&$value) ),+
);
};
}
struct Auth401FeedbackSnapshot<'a> {
request_id: &'a str,
cf_ray: &'a str,
error: &'a str,
error_code: &'a str,
}
impl<'a> Auth401FeedbackSnapshot<'a> {
fn from_optional_fields(
request_id: Option<&'a str>,
cf_ray: Option<&'a str>,
error: Option<&'a str>,
error_code: Option<&'a str>,
) -> Self {
Self {
request_id: request_id.unwrap_or(""),
cf_ray: cf_ray.unwrap_or(""),
error: error.unwrap_or(""),
error_code: error_code.unwrap_or(""),
}
}
}
pub(crate) fn emit_feedback_auth_recovery_tags(
auth_recovery_mode: &str,
auth_recovery_phase: &str,
auth_recovery_outcome: &str,
auth_request_id: Option<&str>,
auth_cf_ray: Option<&str>,
auth_error: Option<&str>,
auth_error_code: Option<&str>,
) {
let auth_401 = Auth401FeedbackSnapshot::from_optional_fields(
auth_request_id,
auth_cf_ray,
auth_error,
auth_error_code,
);
feedback_tags!(
auth_recovery_mode = auth_recovery_mode,
auth_recovery_phase = auth_recovery_phase,
auth_recovery_outcome = auth_recovery_outcome,
auth_401_request_id = auth_401.request_id,
auth_401_cf_ray = auth_401.cf_ray,
auth_401_error = auth_401.error,
auth_401_error_code = auth_401.error_code
);
}
pub fn backoff(attempt: u64) -> Duration {
let exp = BACKOFF_FACTOR.powi(attempt.saturating_sub(1) as i32);
let base = (INITIAL_DELAY_MS as f64 * exp) as u64;
let jitter = rand::rng().random_range(0.9..1.1);
Duration::from_millis((base as f64 * jitter) as u64)
}
pub(crate) fn error_or_panic(message: impl std::string::ToString) {
if cfg!(debug_assertions) {
panic!("{}", message.to_string());
} else {
error!("{}", message.to_string());
}
}
pub fn resolve_path(base: &Path, path: &PathBuf) -> PathBuf {
if path.is_absolute() {
path.clone()
} else {
base.join(path)
}
}
/// Trim a thread name and return `None` if it is empty after trimming.
pub fn normalize_thread_name(name: &str) -> Option<String> {
let trimmed = name.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
#[cfg(test)]
#[path = "util_tests.rs"]
mod tests;