Compare commits

...

3 Commits

Author SHA1 Message Date
kevin zhao
8fb94fb11b fixing image paste 2025-10-26 13:57:02 -07:00
Gabriel Peal
7aab45e060 [MCP] Minor docs clarifications around stdio tokens (#5676)
Noticed
[here](https://github.com/openai/codex/issues/4707#issuecomment-3446547561)
2025-10-26 13:38:30 -04:00
Eric Traut
bcd64c7e72 Reduced runtime of unit test that was taking multiple minutes (#5688)
Modified `build_compacted_history_truncates_overlong_user_messages` test
to reduce runtime from minutes to tens of seconds
2025-10-25 23:46:08 -07:00
4 changed files with 146 additions and 13 deletions

View File

@@ -200,7 +200,20 @@ pub(crate) fn build_compacted_history(
user_messages: &[String],
summary_text: &str,
) -> Vec<ResponseItem> {
let mut history = initial_context;
build_compacted_history_with_limit(
initial_context,
user_messages,
summary_text,
COMPACT_USER_MESSAGE_MAX_TOKENS * 4,
)
}
fn build_compacted_history_with_limit(
mut history: Vec<ResponseItem>,
user_messages: &[String],
summary_text: &str,
max_bytes: usize,
) -> Vec<ResponseItem> {
let mut user_messages_text = if user_messages.is_empty() {
"(none)".to_string()
} else {
@@ -208,7 +221,6 @@ pub(crate) fn build_compacted_history(
};
// Truncate the concatenated prior user messages so the bridge message
// stays well under the context window (approx. 4 bytes/token).
let max_bytes = COMPACT_USER_MESSAGE_MAX_TOKENS * 4;
if user_messages_text.len() > max_bytes {
user_messages_text = truncate_middle(&user_messages_text, max_bytes).0;
}
@@ -361,11 +373,16 @@ mod tests {
#[test]
fn build_compacted_history_truncates_overlong_user_messages() {
// Prepare a very large prior user message so the aggregated
// `user_messages_text` exceeds the truncation threshold used by
// `build_compacted_history` (80k bytes).
let big = "X".repeat(200_000);
let history = build_compacted_history(Vec::new(), std::slice::from_ref(&big), "SUMMARY");
// Use a small truncation limit so the test remains fast while still validating
// that oversized user content is truncated.
let max_bytes = 128;
let big = "X".repeat(max_bytes + 50);
let history = super::build_compacted_history_with_limit(
Vec::new(),
std::slice::from_ref(&big),
"SUMMARY",
max_bytes,
);
// Expect exactly one bridge message added to history (plus any initial context we provided, which is none).
assert_eq!(history.len(), 1);

View File

@@ -52,6 +52,7 @@ use crate::ui_consts::LIVE_PREFIX_COLS;
use codex_file_search::FileMatch;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;
@@ -61,6 +62,34 @@ use std::time::Instant;
/// placeholder in the UI.
const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000;
fn maybe_prefix_root_like(path: &Path) -> Option<PathBuf> {
#[cfg(windows)]
{
let _ = path;
None
}
#[cfg(not(windows))]
{
if path.has_root() {
return None;
}
let path_str = path.to_string_lossy();
const ROOT_PREFIXES: [&str; 5] =
["Applications/", "Library/", "System/", "Users/", "Volumes/"];
if ROOT_PREFIXES
.iter()
.any(|prefix| path_str.starts_with(prefix))
{
return Some(PathBuf::from(format!("/{path_str}")));
}
None
}
}
/// Result returned when the user interacts with the text area.
#[derive(Debug, PartialEq)]
pub enum InputResult {
@@ -275,11 +304,11 @@ impl ChatComposer {
return false;
};
match image::image_dimensions(&path_buf) {
Ok((w, h)) => {
match Self::resolve_image_path_with_fallback(path_buf) {
Ok((resolved_path, w, h)) => {
tracing::info!("OK: {pasted}");
let format_label = pasted_image_format(&path_buf).label();
self.attach_image(path_buf, w, h, format_label);
let format_label = pasted_image_format(&resolved_path).label();
self.attach_image(resolved_path, w, h, format_label);
true
}
Err(err) => {
@@ -289,6 +318,34 @@ impl ChatComposer {
}
}
fn resolve_image_path_with_fallback(
path: PathBuf,
) -> Result<(PathBuf, u32, u32), image::ImageError> {
match image::image_dimensions(&path) {
Ok((w, h)) => Ok((path, w, h)),
Err(err) => {
if let image::ImageError::IoError(io_err) = &err
&& io_err.kind() == ErrorKind::NotFound
{
if let Some(fallback) = maybe_prefix_root_like(&path) {
match image::image_dimensions(&fallback) {
Ok((w, h)) => return Ok((fallback, w, h)),
Err(fallback_err) => {
tracing::debug!(
?fallback_err,
original = %path.display(),
fallback = %fallback.display(),
"fallback_dimensions_failed",
);
}
}
}
}
Err(err)
}
}
}
pub(crate) fn set_disable_paste_burst(&mut self, disabled: bool) {
let was_disabled = self.disable_paste_burst;
self.disable_paste_burst = disabled;
@@ -3448,4 +3505,20 @@ mod tests {
assert_eq!(composer.textarea.text(), "z".repeat(count));
assert!(composer.pending_pastes.is_empty());
}
#[cfg(not(windows))]
#[test]
fn maybe_prefix_root_like_adds_leading_slash() {
let input = PathBuf::from("Users/example/image.png");
let result = maybe_prefix_root_like(&input);
assert_eq!(result, Some(PathBuf::from("/Users/example/image.png")));
}
#[cfg(not(windows))]
#[test]
fn maybe_prefix_root_like_ignores_relative_dirs() {
let input = PathBuf::from("project/assets/image.png");
let result = maybe_prefix_root_like(&input);
assert!(result.is_none());
}
}

View File

@@ -187,12 +187,36 @@ pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
// shell-escaped single path → unescaped
let parts: Vec<String> = shlex::Shlex::new(pasted).collect();
if parts.len() == 1 {
return parts.into_iter().next().map(PathBuf::from);
let mut path = parts.into_iter().next()?;
#[cfg(not(windows))]
{
path = fixup_unix_root_relative_path(path);
}
return Some(PathBuf::from(path));
}
None
}
#[cfg(not(windows))]
fn fixup_unix_root_relative_path(mut path: String) -> String {
use std::path::Path;
if Path::new(&path).has_root() {
return path;
}
const ROOT_PREFIXES: [&str; 5] = ["Applications/", "Library/", "System/", "Users/", "Volumes/"];
if ROOT_PREFIXES.iter().any(|prefix| path.starts_with(prefix)) {
path.insert(0, '/');
}
path
}
/// Infer an image format for the provided path based on its extension.
pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
match path
@@ -255,6 +279,25 @@ mod pasted_paths_tests {
assert!(result.is_none());
}
#[cfg(not(windows))]
#[test]
fn normalize_dragged_finder_users_path() {
let input = "'Users/alice/Pictures/example.png'";
let result = normalize_pasted_path(input).expect("should add leading slash for Users/");
assert_eq!(result, PathBuf::from("/Users/alice/Pictures/example.png"));
}
#[cfg(not(windows))]
#[test]
fn normalize_dragged_finder_volumes_path() {
let input = "'Volumes/ExternalDrive/photos/image.jpg'";
let result = normalize_pasted_path(input).expect("should add leading slash for Volumes/");
assert_eq!(
result,
PathBuf::from("/Volumes/ExternalDrive/photos/image.jpg")
);
}
#[test]
fn pasted_image_format_png_jpeg_unknown() {
assert_eq!(

View File

@@ -417,7 +417,7 @@ cwd = "/Users/<user>/code/my-server"
[mcp_servers.figma]
url = "https://mcp.linear.app/mcp"
# Optional environment variable containing a bearer token to use for auth
bearer_token_env_var = "<token>"
bearer_token_env_var = "ENV_VAR"
# Optional map of headers with hard-coded values.
http_headers = { "HEADER_NAME" = "HEADER_VALUE" }
# Optional map of headers whose values will be replaced with the environment variable.