Skip to main content

eval_magic/adapters/
codex_session.rs

1//! Codex-specific rendering of the skills surface.
2//!
3//! Codex exposes skills with a name,
4//! description, and file path in its initial skills list. Kept separate from
5//! Claude Code's Skill-tool wording so dispatch prompts mirror the harness being
6//! evaluated.
7
8use crate::core::AvailableSkill;
9
10/// Render the discoverable skills the way Codex surfaces them in its initial
11/// skills list: a `## Skills` heading followed by `- name: description (file:
12/// path)` bullets. Returns an empty string when no skills are staged.
13pub fn render_codex_available_skills_block(skills: &[AvailableSkill]) -> String {
14    if skills.is_empty() {
15        return String::new();
16    }
17    let mut sorted: Vec<&AvailableSkill> = skills.iter().collect();
18    sorted.sort_by(|a, b| a.name.cmp(&b.name));
19    let mut out = String::from("## Skills\n");
20    for s in sorted {
21        out.push_str(&format!(
22            "\n- {}: {} (file: {})",
23            s.name, s.description, s.path
24        ));
25    }
26    out
27}
28
29/// Render a Codex plan-mode profile as an operating-context reminder. Codex's
30/// non-interactive `exec` surface has no documented real plan-mode switch, so
31/// this mirrors the portable approximation: text injected ahead of the task.
32pub fn render_codex_plan_mode_context(profile_text: &str) -> String {
33    let trimmed = profile_text.trim();
34    if trimmed.is_empty() {
35        return String::new();
36    }
37    format!("<system-reminder>\n{trimmed}\n</system-reminder>")
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use crate::core::AvailableSkill;
44
45    fn skill(name: &str, description: &str) -> AvailableSkill {
46        AvailableSkill {
47            name: name.into(),
48            path: format!("/x/{name}/SKILL.md"),
49            description: description.into(),
50        }
51    }
52
53    #[test]
54    fn renders_codex_surface_with_name_description_and_file_path() {
55        let block =
56            render_codex_available_skills_block(&[skill("mr-review", "review merge requests")]);
57        assert!(block.contains("## Skills"));
58        assert!(block.contains("- mr-review: review merge requests"));
59        assert!(block.contains("(file: /x/mr-review/SKILL.md)"));
60        assert!(!block.contains("The following skills are available for use with the Skill tool:"));
61    }
62
63    #[test]
64    fn sorts_skills_by_name() {
65        let block =
66            render_codex_available_skills_block(&[skill("zebra", "z"), skill("alpha", "a")]);
67        assert!(block.find("- alpha:").unwrap() < block.find("- zebra:").unwrap());
68    }
69
70    #[test]
71    fn empty_list_renders_empty_string() {
72        assert_eq!(render_codex_available_skills_block(&[]), "");
73    }
74
75    #[test]
76    fn plan_mode_wraps_in_system_reminder_with_codex_profile_text() {
77        let block = render_codex_plan_mode_context("Codex plan mode is active.");
78        assert_eq!(
79            block,
80            "<system-reminder>\nCodex plan mode is active.\n</system-reminder>"
81        );
82    }
83}