Skip to content
Closed
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
76 changes: 75 additions & 1 deletion codex-rs/core/src/custom_prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,77 @@ pub async fn discover_prompts_in_excluding(
Ok(s) => s,
Err(_) => continue,
};
// Extract optional description from simple front matter and strip it from the body.
// We support a minimal subset:
// ---\n
// description: <single line>\n
// ---\n
let (description, body) = parse_front_matter_description_and_body(&content);
out.push(CustomPrompt {
name,
path,
content,
content: body,
description,
});
}
out.sort_by(|a, b| a.name.cmp(&b.name));
out
}

/// Parse a minimal YAML-like front matter from the beginning of `content` and
/// return `(description, body_without_front_matter)`.
///
/// Front matter is recognized only when the very first line is exactly `---` (ignoring
/// surrounding whitespace), and it ends at the next line that is exactly `---`.
/// Within the block, if a line with `description:` is present, its value is captured
/// (with surrounding quotes stripped) and returned.
/// If front matter is malformed (no closing `---`), returns `(None, content.to_string())`.
fn parse_front_matter_description_and_body(content: &str) -> (Option<String>, String) {
let mut lines_iter = content.lines();
match lines_iter.next() {
Some(first) if first.trim() == "---" => {}
_ => return (None, content.to_string()),
}

let mut desc: Option<String> = None;
let mut in_front = true;
let mut body_lines: Vec<&str> = Vec::new();

for line in content.lines().skip(1) {
let trimmed = line.trim();
if in_front {
if trimmed == "---" {
in_front = false;
continue;
}
if let Some(rest) = trimmed.strip_prefix("description:") {
let mut val = rest.trim().to_string();
if (val.starts_with('"') && val.ends_with('"'))
|| (val.starts_with('\'') && val.ends_with('\''))
{
val = val[1..val.len().saturating_sub(1)].to_string();
}
if !val.is_empty() {
desc = Some(val);
}
}
} else {
body_lines.push(line);
}
}

if in_front {
// No closing '---'
return (None, content.to_string());
}

let mut body = body_lines.join("\n");
if content.ends_with('\n') && (!body.is_empty()) {
body.push('\n');
}
(desc, body)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -124,4 +185,17 @@ mod tests {
let names: Vec<String> = found.into_iter().map(|e| e.name).collect();
assert_eq!(names, vec!["good"]);
}

#[tokio::test]
async fn parses_front_matter_description() {
let tmp = tempdir().expect("create TempDir");
let dir = tmp.path();
let content = b"---\ndescription: Hello world\n---\nBody text";
fs::write(dir.join("desc.md"), content).unwrap();
let found = discover_prompts_in(dir).await;
assert_eq!(found.len(), 1);
assert_eq!(found[0].name, "desc");
assert_eq!(found[0].description.as_deref(), Some("Hello world"));
assert_eq!(found[0].content.as_str(), "Body text");
}
}
2 changes: 2 additions & 0 deletions codex-rs/protocol/src/custom_prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ pub struct CustomPrompt {
pub name: String,
pub path: PathBuf,
pub content: String,
/// Optional short description (from front matter) to show in the UI popup.
pub description: Option<String>,
}
Loading