chore(personality) new schema with fallbacks (#10147)

## Summary
Let's dial in this api contract in a bit more with more robust fallback
behavior when model_instructions_template is false.

Switches to a more explicit template / variables structure, with more
fallbacks.

## Testing
- [x] Adding unit tests
- [x] Tested locally
This commit is contained in:
Dylan Hurd
2026-01-30 00:10:12 -07:00
committed by GitHub
parent d550fbf41a
commit e3ab0bd973
13 changed files with 607 additions and 115 deletions

View File

@@ -27,7 +27,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
priority,
upgrade: preset.upgrade.as_ref().map(|u| u.into()),
base_instructions: "base instructions".to_string(),
model_instructions_template: None,
model_messages: None,
supports_reasoning_summaries: false,
support_verbosity: false,
default_verbosity: None,

View File

@@ -31,7 +31,7 @@ use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_BASE_INSTRUCTIONS: &str = "You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI on a user's computer.";
const CODEX_5_2_INSTRUCTIONS_TEMPLATE_DEFAULT: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.";
#[tokio::test]
async fn thread_resume_returns_original_thread() -> Result<()> {
@@ -368,7 +368,7 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> {
}
#[tokio::test]
async fn thread_resume_accepts_personality_override_v2() -> Result<()> {
async fn thread_resume_accepts_personality_override() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
@@ -438,14 +438,14 @@ async fn thread_resume_accepts_personality_override_v2() -> Result<()> {
let request = response_mock.single_request();
let developer_texts = request.message_input_texts("developer");
assert!(
!developer_texts
developer_texts
.iter()
.any(|text| text.contains("<personality_spec>")),
"did not expect a personality update message in developer input, got {developer_texts:?}"
"expected a personality update message in developer input, got {developer_texts:?}"
);
let instructions_text = request.instructions_text();
assert!(
instructions_text.contains(DEFAULT_BASE_INSTRUCTIONS),
instructions_text.contains(CODEX_5_2_INSTRUCTIONS_TEMPLATE_DEFAULT),
"expected default base instructions from history, got {instructions_text:?}"
);
@@ -459,7 +459,7 @@ fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io
config_toml,
format!(
r#"
model = "mock-model"
model = "gpt-5.2-codex"
approval_policy = "never"
sandbox_mode = "read-only"
@@ -467,6 +467,7 @@ model_provider = "mock_provider"
[features]
remote_models = false
personality = true
[model_providers.mock_provider]
name = "Mock provider for test"

View File

@@ -63,7 +63,7 @@ async fn turn_start_sends_originator_header() -> Result<()> {
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::default(),
&BTreeMap::from([(Feature::Personality, true)]),
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -138,7 +138,7 @@ async fn turn_start_emits_user_message_item_with_text_elements() -> Result<()> {
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::default(),
&BTreeMap::from([(Feature::Personality, true)]),
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -230,7 +230,7 @@ async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<(
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::default(),
&BTreeMap::from([(Feature::Personality, true)]),
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -425,7 +425,7 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> {
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::default(),
&BTreeMap::from([(Feature::Personality, true)]),
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -473,6 +473,7 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> {
if developer_texts.is_empty() {
eprintln!("request body: {}", request.body_json());
}
assert!(
developer_texts
.iter()