mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Preserve Apps usage instructions in model catalog messages (#58441)
## What changed Add optional `ModelMessages.apps` and `AppsMessages.usage_instructions` fields to carry model catalog wording without runtime interpretation. Missing or null values become `None`, and strings retain their exact contents, including empty strings, whitespace, and template-like text. ## Testing Add serialization coverage for nullable and literal Apps values, and extend existing tests to verify Apps messages survive model response round trips and base instruction overrides. GitOrigin-RevId: 49b516656255e07b91f8f4679e2acb8e1b03f9e1
This commit is contained in:
@@ -31,6 +31,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
|
||||
available_access_programs: preset.available_access_programs.clone(),
|
||||
upgrade: preset.upgrade.as_ref().map(Into::into),
|
||||
model_messages: Some(ModelMessages {
|
||||
apps: None,
|
||||
content_filter_guidance: None,
|
||||
persistent_instructions: None,
|
||||
tools: None,
|
||||
|
||||
@@ -40,6 +40,7 @@ async fn catalog_permission_message_loaded_from_remote_models_is_sent(
|
||||
let model_slug = "remote-catalog-permissions-model";
|
||||
let mut model = model_info_from_slug(model_slug);
|
||||
model.model_messages = Some(ModelMessages {
|
||||
apps: None,
|
||||
content_filter_guidance: None,
|
||||
persistent_instructions: None,
|
||||
tools: None,
|
||||
|
||||
@@ -74,6 +74,7 @@ fn model_with_collaboration_messages(
|
||||
) -> codex_protocol::openai_models::ModelInfo {
|
||||
let mut model = model_info_from_slug(slug);
|
||||
let model_messages = model.model_messages.get_or_insert(ModelMessages {
|
||||
apps: None,
|
||||
content_filter_guidance: None,
|
||||
persistent_instructions: None,
|
||||
tools: None,
|
||||
|
||||
@@ -643,6 +643,7 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo {
|
||||
available_access_programs: None,
|
||||
upgrade: None,
|
||||
model_messages: Some(ModelMessages {
|
||||
apps: None,
|
||||
content_filter_guidance: None,
|
||||
persistent_instructions: None,
|
||||
tools: None,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::ModelsManagerConfig;
|
||||
use codex_prompts::render_model_instructions;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::openai_models::ApprovalMessages;
|
||||
use codex_protocol::openai_models::AppsMessages;
|
||||
use codex_protocol::openai_models::AutoReviewMessages;
|
||||
use codex_protocol::openai_models::CollaborationModeMessages;
|
||||
use codex_protocol::openai_models::ConfirmationPolicies;
|
||||
@@ -90,6 +91,9 @@ fn base_instruction_override_is_literal_and_preserves_catalog_messages() {
|
||||
computer_use: Some(" # Native policy\r\n\n${native_markdown}\n".to_string()),
|
||||
};
|
||||
let mut messages = ModelMessages {
|
||||
apps: Some(AppsMessages {
|
||||
usage_instructions: Some("Catalog Apps instructions.".to_string()),
|
||||
}),
|
||||
content_filter_guidance: None,
|
||||
persistent_instructions: Some(persistent_instructions.to_string()),
|
||||
tools: Some(ToolMessages {
|
||||
|
||||
@@ -580,6 +580,9 @@ impl ModelInfo {
|
||||
/// retained for model catalog compatibility.
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct ModelMessages {
|
||||
/// Optional model-owned Apps wording, transported without runtime interpretation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub apps: Option<AppsMessages>,
|
||||
/// Developer guidance after a content-filter block. Missing, null, blank, or values over
|
||||
/// 512 UTF-8 bytes use the bundled guidance.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -605,6 +608,15 @@ pub struct ModelMessages {
|
||||
pub confirmation_policies: Option<ConfirmationPolicies>,
|
||||
}
|
||||
|
||||
/// Optional Apps wording from the model catalog, preserved for future runtime consumption.
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct AppsMessages {
|
||||
/// Missing and null deserialize as `None`; all strings preserve their exact bytes.
|
||||
/// Rendering, size limits, and empty-string behavior are defined by consumers.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub usage_instructions: Option<String>,
|
||||
}
|
||||
|
||||
/// Model-owned confirmation-policy Markdown, forwarded unchanged to actor tools.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct ConfirmationPolicies {
|
||||
@@ -1225,6 +1237,32 @@ mod tests {
|
||||
assert_eq!(messages, ModelMessages::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apps_messages_preserve_nullable_and_literal_wire_values() {
|
||||
for apps in [
|
||||
serde_json::Value::Null,
|
||||
serde_json::json!({}),
|
||||
serde_json::json!({"usage_instructions": null}),
|
||||
serde_json::json!({"usage_instructions": ""}),
|
||||
serde_json::json!({"usage_instructions": " \r\n\t"}),
|
||||
serde_json::json!({"usage_instructions": " {{literal}}\r\n"}),
|
||||
serde_json::json!({"usage_instructions": "é".repeat(501)}),
|
||||
] {
|
||||
let messages: ModelMessages =
|
||||
serde_json::from_value(serde_json::json!({"apps": apps})).unwrap();
|
||||
let serialized = serde_json::to_value(messages).unwrap();
|
||||
let mut expected = serde_json::to_value(ModelMessages::default()).unwrap();
|
||||
if !apps.is_null() {
|
||||
expected["apps"] = if apps["usage_instructions"].is_null() {
|
||||
serde_json::json!({})
|
||||
} else {
|
||||
apps
|
||||
};
|
||||
}
|
||||
assert_eq!(serialized, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_user_message_async_description_preserves_missing_null_and_empty_values() {
|
||||
for (value, expected) in [
|
||||
@@ -1723,6 +1761,9 @@ mod tests {
|
||||
#[test]
|
||||
fn models_response_prefers_template_and_preserves_message_siblings() {
|
||||
let messages = ModelMessages {
|
||||
apps: Some(AppsMessages {
|
||||
usage_instructions: Some("Literal Apps {{guidance}}".to_string()),
|
||||
}),
|
||||
content_filter_guidance: Some("Offer a permitted alternative.".to_string()),
|
||||
persistent_instructions: Some("Persistent catalog instructions".to_string()),
|
||||
tools: Some(ToolMessages {
|
||||
|
||||
Reference in New Issue
Block a user