Use model catalog approval messages (#31312)

## Why

Approval guidance is currently assembled entirely by the client. Model
Messages V2 needs model catalogs to provide model-specific `on_request`
guidance for both user-reviewed and auto-reviewed approval flows while
retaining the existing generated prompt as a compatibility fallback.

## What changed

- add nullable `on_request` and `on_request_auto_review` catalog
messages
- select the message matching the active approvals reviewer for
`on_request` policies
- replace the complete legacy approval section when the selected catalog
value exists, including support for an empty string that suppresses the
section
- retain legacy rendering when the object or selected key is absent, and
for non-`on_request` policies
- preserve approval messages when base-instruction or personality
overrides clear instruction templates
- refresh permissions instructions when the active model changes
- pass catalog messages through initial and incremental permissions
construction

## Relationship to reviewer persistence

PR #31309 independently persists the approvals reviewer in turn context
and refreshes permissions when that reviewer changes. This PR is based
directly on `main` and does not duplicate that rollout migration; once
both land, reviewer switches will also select and append the new catalog
variant.

## Testing

- `just test -p codex-protocol`
- `just test -p codex-prompts`
- `just test -p codex-models-manager`
- `just test -p codex-core permissions_messages`
This commit is contained in:
Dylan Hurd authored and GitHub committed 2026-07-07 10:52:38 -07:00
1 parent 1345c16dd7
commit 358575465c
13 files changed
+419 -20

No files matched your search

+42
View File
@@ -485,6 +485,13 @@ impl ModelInfo {
pub struct ModelMessages {
pub instructions_template: Option<String>,
pub instructions_variables: Option<ModelInstructionsVariables>,
pub approvals: Option<ApprovalMessages>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
pub struct ApprovalMessages {
pub on_request: Option<String>,
pub on_request_auto_review: Option<String>,
}
impl ModelMessages {
@@ -704,6 +711,37 @@ mod tests {
}
}
#[test]
fn model_messages_deserialize_without_approvals() {
let messages: ModelMessages =
from_str(r#"{"instructions_template":null,"instructions_variables":null}"#)
.expect("model messages should deserialize");
assert_eq!(messages.approvals, None);
}
#[test]
fn approval_messages_preserve_missing_and_empty_values() {
let messages: ModelMessages = from_str(
r#"{
"instructions_template": null,
"instructions_variables": null,
"approvals": {
"on_request": ""
}
}"#,
)
.expect("approval messages should deserialize");
assert_eq!(
messages.approvals,
Some(ApprovalMessages {
on_request: Some(String::new()),
on_request_auto_review: None,
})
);
}
#[test]
fn reasoning_effort_accepts_known_and_custom_values() {
let custom = ReasoningEffort::Custom("future".to_string());
@@ -775,6 +813,7 @@ mod tests {
let model = test_model(Some(ModelMessages {
instructions_template: Some("Hello {{ personality }}".to_string()),
instructions_variables: Some(personality_variables()),
approvals: None,
}));
let instructions = model.get_model_instructions(Some(Personality::Friendly));
@@ -791,6 +830,7 @@ mod tests {
personality_friendly: Some("friendly".to_string()),
personality_pragmatic: None,
}),
approvals: None,
}));
assert_eq!(
model.get_model_instructions(Some(Personality::Friendly)),
@@ -816,6 +856,7 @@ mod tests {
personality_friendly: None,
personality_pragmatic: None,
}),
approvals: None,
}));
assert_eq!(
model_no_personality.get_model_instructions(Some(Personality::Friendly)),
@@ -844,6 +885,7 @@ mod tests {
personality_friendly: None,
personality_pragmatic: None,
}),
approvals: None,
}));
let instructions = model.get_model_instructions(Some(Personality::Friendly));