diff --git a/codex-rs/core/src/context/contextual_user_message_tests.rs b/codex-rs/core/src/context/contextual_user_message_tests.rs
index c3f360772a..dae0ff313b 100644
--- a/codex-rs/core/src/context/contextual_user_message_tests.rs
+++ b/codex-rs/core/src/context/contextual_user_message_tests.rs
@@ -47,6 +47,43 @@ fn detects_internal_model_context_fragment() {
}));
}
+#[test]
+fn parses_canonical_internal_model_context_fragment() {
+ for (source, body) in [
+ ("goal", "Keep working toward the user-provided objective."),
+ ("extension", "\nPreserve body whitespace exactly.\n"),
+ ] {
+ let rendered =
+ InternalModelContextFragment::new(InternalContextSource::from_static(source), body)
+ .render();
+
+ let parsed = InternalModelContextFragment::parse_canonical(&rendered)
+ .expect("canonical internal context should parse");
+
+ assert_eq!(parsed.source().as_str(), source);
+ assert_eq!(parsed.body(), body);
+ }
+
+ assert_eq!(
+ InternalModelContextFragment::parse_canonical(
+ "\nbody\n"
+ ),
+ None
+ );
+ assert_eq!(
+ InternalModelContextFragment::parse_canonical(
+ "\nbody\n"
+ ),
+ None
+ );
+ assert_eq!(
+ InternalModelContextFragment::parse_canonical(
+ "\nContinue working toward the active thread goal.\n"
+ ),
+ None
+ );
+}
+
#[test]
fn detects_legacy_goal_context_fragment() {
assert!(is_contextual_user_fragment(&ContentItem::InputText {
diff --git a/codex-rs/core/src/context/internal_model_context.rs b/codex-rs/core/src/context/internal_model_context.rs
index cbba3ecd75..2966a11994 100644
--- a/codex-rs/core/src/context/internal_model_context.rs
+++ b/codex-rs/core/src/context/internal_model_context.rs
@@ -73,6 +73,32 @@ impl InternalModelContextFragment {
body: body.into(),
}
}
+
+ pub(crate) fn parse_canonical(text: &str) -> Option {
+ let rest = text.strip_prefix(CONTEXT_START_MARKER)?;
+ let rest = rest.strip_prefix(SOURCE_ATTR_START)?;
+ let (source, body_and_close) = rest.split_once(SOURCE_ATTR_END)?;
+ if !is_valid_source(source) {
+ return None;
+ }
+
+ let body = body_and_close.strip_suffix(CONTEXT_END_MARKER)?;
+ let body = body.strip_prefix('\n').unwrap_or(body);
+ let body = body.strip_suffix('\n').unwrap_or(body);
+
+ Some(Self::new(
+ InternalContextSource(source.to_string()),
+ body.to_string(),
+ ))
+ }
+
+ pub(crate) fn source(&self) -> &InternalContextSource {
+ &self.source
+ }
+
+ pub(crate) fn body(&self) -> &str {
+ &self.body
+ }
}
impl ContextualUserFragment for InternalModelContextFragment {
@@ -94,22 +120,12 @@ impl ContextualUserFragment for InternalModelContextFragment {
return true;
}
- let Some(rest) = trimmed.strip_prefix(CONTEXT_START_MARKER) else {
- return false;
- };
- let Some(rest) = rest.strip_prefix(SOURCE_ATTR_START) else {
- return false;
- };
- let Some((source, body_and_close)) = rest.split_once(SOURCE_ATTR_END) else {
- return false;
- };
-
- is_valid_source(source) && body_and_close.ends_with(CONTEXT_END_MARKER)
+ Self::parse_canonical(trimmed).is_some()
}
fn body(&self) -> String {
- let source = self.source.as_str();
- let body = &self.body;
+ let source = self.source().as_str();
+ let body = self.body();
format!(" source=\"{source}\">\n{body}\n")
}
}