agents doc path passed as sessionconfigured instead

This commit is contained in:
pap
2025-08-06 22:38:38 +01:00
parent a7540b5abe
commit 09e1ec811e
9 changed files with 129 additions and 3 deletions

View File

@@ -1,17 +1,23 @@
use codex_core::WireApi;
use codex_core::agents_doc_path_string;
use codex_core::config::Config;
use crate::sandbox_summary::summarize_sandbox_policy;
/// Build a list of key/value pairs summarizing the effective configuration.
pub fn create_config_summary_entries(config: &Config) -> Vec<(&'static str, String)> {
let mut entries = vec![
("workdir", config.cwd.display().to_string()),
let mut entries = vec![("workdir", config.cwd.display().to_string())];
entries.extend([
(
"agents.md",
agents_doc_path_string(config).unwrap_or_else(|| "none".to_string()),
),
("model", config.model.clone()),
("provider", config.model_provider_id.clone()),
("approval", config.approval_policy.to_string()),
("sandbox", summarize_sandbox_policy(&config.sandbox_policy)),
];
]);
if config.model_provider.wire_api == WireApi::Responses
&& config.model_family.supports_reasoning_summaries
{

View File

@@ -857,6 +857,7 @@ async fn submission_loop(
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
session_id,
model,
agents_doc_path: crate::project_doc::agents_doc_path_string(&config),
history_log_id,
history_entry_count,
}),

View File

@@ -39,6 +39,7 @@ mod openai_model_info;
mod openai_tools;
pub mod plan_tool;
mod project_doc;
pub use project_doc::agents_doc_path_string;
pub use project_doc::discover_project_doc_path;
pub mod protocol;
mod rollout;

View File

@@ -13,6 +13,7 @@
use crate::config::Config;
use std::path::Path;
use std::path::PathBuf;
use tokio::io::AsyncReadExt;
use tracing::error;
@@ -128,6 +129,113 @@ pub(crate) async fn get_user_instructions(config: &Config) -> Option<String> {
}
}
/// Return a humanreadable description of the AGENTS.md path(s) that will be
/// loaded for this session, or `None` if neither global nor project docs are
/// present.
///
/// This mirrors the discovery logic used by `get_user_instructions()`:
/// - If `~/.codex/AGENTS.md` (global) is nonempty, it is included.
/// - If a project AGENTS.md is found (respecting the byte limit and git root
/// stop), it is included.
/// - When the project_doc_max_bytes is set to 0, project docs are disabled.
pub fn agents_doc_path_string(config: &Config) -> Option<String> {
let mut parts: Vec<String> = Vec::new();
// Global AGENTS.md in CODEX_HOME.
if config.user_instructions.is_some() {
let global = config.codex_home.join("AGENTS.md");
parts.push(global.display().to_string());
}
// Project AGENTS.md, unless disabled via bytelimit == 0.
if config.project_doc_max_bytes > 0 {
if let Some(p) = find_project_doc_path_sync(config) {
parts.push(p.display().to_string());
}
}
if parts.is_empty() {
None
} else {
Some(parts.join(" + "))
}
}
/// Synchronous counterpart to `find_project_doc()` that returns the discovered
/// path rather than the contents. Skips empty files (after trimming) to stay
/// consistent with `load_first_candidate()`.
fn find_project_doc_path_sync(config: &Config) -> Option<PathBuf> {
// Attempt to locate in cwd first.
if let Some(p) = first_nonempty_candidate_in_dir(&config.cwd, CANDIDATE_FILENAMES) {
return Some(p);
}
// Walk up to git root.
let mut dir = config.cwd.clone();
if let Ok(canon) = dir.canonicalize() {
dir = canon;
}
while let Some(parent) = dir.parent() {
let git_marker = dir.join(".git");
let git_exists = std::fs::metadata(&git_marker).is_ok();
if git_exists {
if let Some(p) = first_nonempty_candidate_in_dir(&dir, CANDIDATE_FILENAMES) {
return Some(p);
}
break; // do not walk past git root
}
dir = parent.to_path_buf();
}
None
}
/// Return the first path in `dir` that matches any of `names` and is nonempty
/// (after trimming). Returns `None` if no such file exists.
fn first_nonempty_candidate_in_dir(dir: &Path, names: &[&str]) -> Option<PathBuf> {
for name in names {
let candidate = dir.join(name);
// Fast path: must exist and be a file.
let md = match std::fs::metadata(&candidate) {
Ok(m) if m.is_file() => m,
_ => continue,
};
// If the file is zero bytes, skip without reading.
if md.len() == 0 {
continue;
}
// Read up to a modest limit to determine if the contents are
// effectively empty after trimming. Use the same limit as
// `project_doc_max_bytes` would permit by default: however, the goal is
// to avoid reading arbitrarily large files here just for display. Read
// up to 8 KiB which should be sufficient to determine nonemptiness
// after trimming whitespace.
const MAX_PEEK_BYTES: usize = 8 * 1024;
let mut file = match std::fs::File::open(&candidate) {
Ok(f) => f,
Err(_) => continue,
};
use std::io::Read as _;
let mut buf = Vec::with_capacity(std::cmp::min(md.len() as usize, MAX_PEEK_BYTES));
if std::io::Read::by_ref(&mut file)
.take(MAX_PEEK_BYTES as u64)
.read_to_end(&mut buf)
.is_err()
{
continue;
}
let s = String::from_utf8_lossy(&buf);
if s.trim().is_empty() {
continue;
}
return Some(candidate);
}
None
}
/// Attempt to locate and load the project documentation. Currently, the search
/// starts from `Config::cwd`, but if we may want to consider other directories
/// in the future, e.g., additional writable directories in the `SandboxPolicy`.

View File

@@ -642,6 +642,10 @@ pub struct SessionConfiguredEvent {
/// Tell the client what model is being queried.
pub model: String,
/// The path(s) to AGENTS.md that were loaded for this session, if any.
#[serde(skip_serializing_if = "Option::is_none")]
pub agents_doc_path: Option<String>,
/// Identifier of the history log file (inode on Unix, 0 otherwise).
pub history_log_id: u64,
@@ -707,6 +711,7 @@ mod tests {
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
session_id,
model: "codex-mini-latest".to_string(),
agents_doc_path: None,
history_log_id: 0,
history_entry_count: 0,
}),

View File

@@ -494,6 +494,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
model,
history_log_id: _,
history_entry_count: _,
..
} = session_configured_event;
ts_println!(

View File

@@ -906,6 +906,7 @@ mod tests {
msg: EventMsg::SessionConfigured(codex_core::protocol::SessionConfiguredEvent {
session_id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
model: "codex-mini-latest".into(),
agents_doc_path: None,
history_log_id: 42,
history_entry_count: 3,
}),

View File

@@ -242,6 +242,7 @@ mod tests {
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
session_id: Uuid::new_v4(),
model: "gpt-4o".to_string(),
agents_doc_path: None,
history_log_id: 1,
history_entry_count: 1000,
}),
@@ -282,6 +283,7 @@ mod tests {
let session_configured_event = SessionConfiguredEvent {
session_id: Uuid::new_v4(),
model: "gpt-4o".to_string(),
agents_doc_path: None,
history_log_id: 1,
history_entry_count: 1000,
};

View File

@@ -169,6 +169,7 @@ impl HistoryCell {
session_id,
history_log_id: _,
history_entry_count: _,
..
} = event;
if is_first_event {
const VERSION: &str = env!("CARGO_PKG_VERSION");