mirror of
https://github.com/openai/codex.git
synced 2026-04-28 00:25:56 +00:00
move plugin/skill instructions into dev msg and reorder (#14609)
Move the general `Apps`, `Skills` and `Plugins` instructions blocks out of `user_instructions` and into the developer message, with new `Apps -> Skills -> Plugins` order for better clarity. Also wrap those sections in stable XML-style instruction tags (like other sections) and update prompt-layout tests/snapshots. This makes the tests less brittle in snapshot output (we can parse the sections), and it consolidates the capability instructions in one place. #### Tests Updated snapshots, added tests. `<AGENTS_MD>` disappearing in snapshots is expected: before this change, the wrapped user-instructions message was kept alive by `Skills` content. Now that `Skills` and `Plugins` are in the developer message, that wrapper only appears when there is real project-doc/user-instructions content. --------- Co-authored-by: Charley Cunningham <ccunningham@openai.com>
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
use regex_lite::Regex;
|
||||
use serde_json::Value;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::responses::ResponsesRequest;
|
||||
use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum ContextSnapshotRenderMode {
|
||||
@@ -16,12 +21,14 @@ pub enum ContextSnapshotRenderMode {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextSnapshotOptions {
|
||||
render_mode: ContextSnapshotRenderMode,
|
||||
strip_capability_instructions: bool,
|
||||
}
|
||||
|
||||
impl Default for ContextSnapshotOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
render_mode: ContextSnapshotRenderMode::RedactedText,
|
||||
strip_capability_instructions: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +38,11 @@ impl ContextSnapshotOptions {
|
||||
self.render_mode = render_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn strip_capability_instructions(mut self) -> Self {
|
||||
self.strip_capability_instructions = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_request_input_snapshot(
|
||||
@@ -68,17 +80,23 @@ pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshot
|
||||
.map(|content| {
|
||||
content
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
.filter_map(|entry| {
|
||||
if let Some(text) = entry.get("text").and_then(Value::as_str) {
|
||||
return format_snapshot_text(text, options);
|
||||
if options.strip_capability_instructions
|
||||
&& role == "developer"
|
||||
&& is_capability_instruction_text(text)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
return Some(format_snapshot_text(text, options));
|
||||
}
|
||||
let Some(content_type) =
|
||||
entry.get("type").and_then(Value::as_str)
|
||||
else {
|
||||
return "<UNKNOWN_CONTENT_ITEM>".to_string();
|
||||
return Some("<UNKNOWN_CONTENT_ITEM>".to_string());
|
||||
};
|
||||
let Some(content_object) = entry.as_object() else {
|
||||
return format!("<{content_type}>");
|
||||
return Some(format!("<{content_type}>"));
|
||||
};
|
||||
let mut extra_keys = content_object
|
||||
.keys()
|
||||
@@ -86,11 +104,11 @@ pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshot
|
||||
.cloned()
|
||||
.collect::<Vec<String>>();
|
||||
extra_keys.sort();
|
||||
if extra_keys.is_empty() {
|
||||
Some(if extra_keys.is_empty() {
|
||||
format!("<{content_type}>")
|
||||
} else {
|
||||
format!("<{content_type}:{}>", extra_keys.join(","))
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
@@ -241,6 +259,15 @@ fn canonicalize_snapshot_text(text: &str) -> String {
|
||||
if text.starts_with("<permissions instructions>") {
|
||||
return "<PERMISSIONS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with(APPS_INSTRUCTIONS_OPEN_TAG) {
|
||||
return "<APPS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with(SKILLS_INSTRUCTIONS_OPEN_TAG) {
|
||||
return "<SKILLS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with(PLUGINS_INSTRUCTIONS_OPEN_TAG) {
|
||||
return "<PLUGINS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with("# AGENTS.md instructions for ") {
|
||||
return "<AGENTS_MD>".to_string();
|
||||
}
|
||||
@@ -282,7 +309,24 @@ fn canonicalize_snapshot_text(text: &str) -> String {
|
||||
{
|
||||
return format!("<COMPACTION_SUMMARY>\n{summary}");
|
||||
}
|
||||
text.to_string()
|
||||
normalize_dynamic_snapshot_paths(text)
|
||||
}
|
||||
|
||||
fn is_capability_instruction_text(text: &str) -> bool {
|
||||
text.starts_with(APPS_INSTRUCTIONS_OPEN_TAG)
|
||||
|| text.starts_with(SKILLS_INSTRUCTIONS_OPEN_TAG)
|
||||
|| text.starts_with(PLUGINS_INSTRUCTIONS_OPEN_TAG)
|
||||
}
|
||||
|
||||
fn normalize_dynamic_snapshot_paths(text: &str) -> String {
|
||||
static SYSTEM_SKILL_PATH_RE: OnceLock<Regex> = OnceLock::new();
|
||||
let system_skill_path_re = SYSTEM_SKILL_PATH_RE.get_or_init(|| {
|
||||
Regex::new(r"/[^)\n]*/skills/\.system/([^/\n]+)/SKILL\.md")
|
||||
.expect("system skill path regex should compile")
|
||||
});
|
||||
system_skill_path_re
|
||||
.replace_all(text, "<SYSTEM_SKILLS_ROOT>/$1/SKILL.md")
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -353,6 +397,60 @@ mod tests {
|
||||
assert_eq!(rendered, "00:message/user:<AGENTS_MD>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redacted_text_mode_keeps_capability_instruction_placeholders() {
|
||||
let items = vec![json!({
|
||||
"type": "message",
|
||||
"role": "developer",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "<apps_instructions>\n## Apps\nbody\n</apps_instructions>"
|
||||
},
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "<skills_instructions>\n## Skills\nbody\n</skills_instructions>"
|
||||
},
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "<plugins_instructions>\n## Plugins\nbody\n</plugins_instructions>"
|
||||
}
|
||||
]
|
||||
})];
|
||||
|
||||
let rendered = format_response_items_snapshot(
|
||||
&items,
|
||||
&ContextSnapshotOptions::default().render_mode(ContextSnapshotRenderMode::RedactedText),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
"00:message/developer[3]:\n [01] <APPS_INSTRUCTIONS>\n [02] <SKILLS_INSTRUCTIONS>\n [03] <PLUGINS_INSTRUCTIONS>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_capability_instructions_omits_capability_parts_from_developer_messages() {
|
||||
let items = vec![json!({
|
||||
"type": "message",
|
||||
"role": "developer",
|
||||
"content": [
|
||||
{ "type": "input_text", "text": "<permissions instructions>\n...</permissions instructions>" },
|
||||
{ "type": "input_text", "text": "<skills_instructions>\n## Skills\n...</skills_instructions>" },
|
||||
{ "type": "input_text", "text": "<plugins_instructions>\n## Plugins\n...</plugins_instructions>" }
|
||||
]
|
||||
})];
|
||||
|
||||
let rendered = format_response_items_snapshot(
|
||||
&items,
|
||||
&ContextSnapshotOptions::default()
|
||||
.render_mode(ContextSnapshotRenderMode::RedactedText)
|
||||
.strip_capability_instructions(),
|
||||
);
|
||||
|
||||
assert_eq!(rendered, "00:message/developer:<PERMISSIONS_INSTRUCTIONS>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redacted_text_mode_normalizes_environment_context_with_subagents() {
|
||||
let items = vec![json!({
|
||||
@@ -442,4 +540,23 @@ mod tests {
|
||||
"00:message/user[3]:\n [01] <image>\n [02] <input_image:image_url>\n [03] </image>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redacted_text_mode_normalizes_system_skill_temp_paths() {
|
||||
let items = vec![json!({
|
||||
"type": "message",
|
||||
"role": "developer",
|
||||
"content": [{
|
||||
"type": "input_text",
|
||||
"text": "## Skills\n- openai-docs: helper (file: /private/var/folders/yk/p4jp9nzs79s5q84csslkgqtm0000gn/T/.tmpAnGVww/skills/.system/openai-docs/SKILL.md)"
|
||||
}]
|
||||
})];
|
||||
|
||||
let rendered = format_response_items_snapshot(&items, &ContextSnapshotOptions::default());
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
"00:message/developer:## Skills\\n- openai-docs: helper (file: <SYSTEM_SKILLS_ROOT>/openai-docs/SKILL.md)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user