mirror of
https://github.com/openai/codex.git
synced 2026-02-07 01:13:40 +00:00
Compare commits
3 Commits
pakrym/log
...
fix/image-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fb94fb11b | ||
|
|
7aab45e060 | ||
|
|
bcd64c7e72 |
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user