Use model catalog collaboration mode messages (#36351)

## What changed

- Add optional `default` and `plan` collaboration-mode messages to model catalog metadata.
- Prefer the active mode's catalog message, including an explicit empty value, while falling back to the existing developer instructions when the catalog variant is absent.
- Track the model in collaboration-mode world state so mode and model changes append the appropriate instructions, and clear prior instructions when no current message exists.
- Preserve catalog collaboration messages when instruction overrides remove other model instruction fields.

## Testing

- Cover catalog deserialization, variant selection, fallback and empty-message behavior, legacy snapshot migration, mode changes, and model changes.

GitOrigin-RevId: d4c6c0028423bc50b0e8175e55586f3e7bc1c3a8
This commit is contained in:
rhan-oai authored and copyberry committed 2026-07-31 15:40:52 +00:00
1 parent 7b38c48da9
commit 2c005abb07
13 files changed
+556 -36

No files matched your search

+54 -3
View File
@@ -507,6 +507,7 @@ pub struct ModelMessages {
pub instructions_template: Option<String>,
pub instructions_variables: Option<ModelInstructionsVariables>,
pub approvals: Option<ApprovalMessages>,
pub collaboration_modes: Option<CollaborationModeMessages>,
pub auto_review: Option<AutoReviewMessages>,
pub permissions: Option<PermissionMessages>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -531,6 +532,12 @@ pub struct ApprovalMessages {
pub unless_trusted: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
pub struct CollaborationModeMessages {
pub default: Option<String>,
pub plan: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
pub struct AutoReviewMessages {
pub policy: Option<String>,
@@ -763,13 +770,23 @@ mod tests {
}
#[test]
fn model_messages_deserialize_without_approvals() {
fn model_messages_deserialize_without_optional_sections() {
let messages: ModelMessages =
from_str(r#"{"instructions_template":null,"instructions_variables":null}"#)
.expect("model messages should deserialize");
assert_eq!(messages.approvals, None);
assert_eq!(messages.permissions, None);
assert_eq!(
messages,
ModelMessages {
instructions_template: None,
instructions_variables: None,
approvals: None,
collaboration_modes: None,
auto_review: None,
permissions: None,
token_budget: None,
}
);
}
#[test]
@@ -860,6 +877,36 @@ mod tests {
);
}
#[test]
fn collaboration_mode_messages_preserve_missing_and_empty_values() {
let messages: ModelMessages = from_str(
r#"{
"instructions_template": null,
"instructions_variables": null,
"collaboration_modes": {
"default": ""
}
}"#,
)
.expect("collaboration mode messages should deserialize");
assert_eq!(
messages,
ModelMessages {
instructions_template: None,
instructions_variables: None,
approvals: None,
collaboration_modes: Some(CollaborationModeMessages {
default: Some(String::new()),
plan: None,
}),
auto_review: None,
permissions: None,
token_budget: None,
}
);
}
#[test]
fn reasoning_effort_accepts_known_and_custom_values() {
let custom = ReasoningEffort::Custom("future".to_string());
@@ -932,6 +979,7 @@ mod tests {
instructions_template: Some("Hello {{ personality }}".to_string()),
instructions_variables: Some(personality_variables()),
approvals: None,
collaboration_modes: None,
auto_review: None,
permissions: None,
token_budget: None,
@@ -952,6 +1000,7 @@ mod tests {
personality_pragmatic: None,
}),
approvals: None,
collaboration_modes: None,
auto_review: None,
permissions: None,
token_budget: None,
@@ -981,6 +1030,7 @@ mod tests {
personality_pragmatic: None,
}),
approvals: None,
collaboration_modes: None,
auto_review: None,
permissions: None,
token_budget: None,
@@ -1013,6 +1063,7 @@ mod tests {
personality_pragmatic: None,
}),
approvals: None,
collaboration_modes: None,
auto_review: None,
permissions: None,
token_budget: None,