feat: compaction prompt configurable (#5959)
``` codex -c compact_prompt="Summarize in bullet points" ```
This commit is contained in:
@@ -321,6 +321,10 @@ pub struct NewConversationParams {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub base_instructions: Option<String>,
|
pub base_instructions: Option<String>,
|
||||||
|
|
||||||
|
/// Prompt used during conversation compaction.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub compact_prompt: Option<String>,
|
||||||
|
|
||||||
/// Whether to include the apply patch tool in the conversation.
|
/// Whether to include the apply patch tool in the conversation.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub include_apply_patch_tool: Option<bool>,
|
pub include_apply_patch_tool: Option<bool>,
|
||||||
@@ -1125,6 +1129,7 @@ mod tests {
|
|||||||
sandbox: None,
|
sandbox: None,
|
||||||
config: None,
|
config: None,
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
include_apply_patch_tool: None,
|
include_apply_patch_tool: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1760,6 +1760,7 @@ async fn derive_config_from_params(
|
|||||||
sandbox: sandbox_mode,
|
sandbox: sandbox_mode,
|
||||||
config: cli_overrides,
|
config: cli_overrides,
|
||||||
base_instructions,
|
base_instructions,
|
||||||
|
compact_prompt,
|
||||||
include_apply_patch_tool,
|
include_apply_patch_tool,
|
||||||
} = params;
|
} = params;
|
||||||
let overrides = ConfigOverrides {
|
let overrides = ConfigOverrides {
|
||||||
@@ -1772,6 +1773,7 @@ async fn derive_config_from_params(
|
|||||||
model_provider,
|
model_provider,
|
||||||
codex_linux_sandbox_exe,
|
codex_linux_sandbox_exe,
|
||||||
base_instructions,
|
base_instructions,
|
||||||
|
compact_prompt,
|
||||||
include_apply_patch_tool,
|
include_apply_patch_tool,
|
||||||
include_view_image_tool: None,
|
include_view_image_tool: None,
|
||||||
show_raw_agent_reasoning: None,
|
show_raw_agent_reasoning: None,
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ impl Codex {
|
|||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
user_instructions,
|
user_instructions,
|
||||||
base_instructions: config.base_instructions.clone(),
|
base_instructions: config.base_instructions.clone(),
|
||||||
|
compact_prompt: config.compact_prompt.clone(),
|
||||||
approval_policy: config.approval_policy,
|
approval_policy: config.approval_policy,
|
||||||
sandbox_policy: config.sandbox_policy.clone(),
|
sandbox_policy: config.sandbox_policy.clone(),
|
||||||
cwd: config.cwd.clone(),
|
cwd: config.cwd.clone(),
|
||||||
@@ -265,6 +266,7 @@ pub(crate) struct TurnContext {
|
|||||||
/// instead of `std::env::current_dir()`.
|
/// instead of `std::env::current_dir()`.
|
||||||
pub(crate) cwd: PathBuf,
|
pub(crate) cwd: PathBuf,
|
||||||
pub(crate) base_instructions: Option<String>,
|
pub(crate) base_instructions: Option<String>,
|
||||||
|
pub(crate) compact_prompt: Option<String>,
|
||||||
pub(crate) user_instructions: Option<String>,
|
pub(crate) user_instructions: Option<String>,
|
||||||
pub(crate) approval_policy: AskForApproval,
|
pub(crate) approval_policy: AskForApproval,
|
||||||
pub(crate) sandbox_policy: SandboxPolicy,
|
pub(crate) sandbox_policy: SandboxPolicy,
|
||||||
@@ -281,6 +283,12 @@ impl TurnContext {
|
|||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.map_or_else(|| self.cwd.clone(), |p| self.cwd.join(p))
|
.map_or_else(|| self.cwd.clone(), |p| self.cwd.join(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compact_prompt(&self) -> &str {
|
||||||
|
self.compact_prompt
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(compact::SUMMARIZATION_PROMPT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -301,6 +309,9 @@ pub(crate) struct SessionConfiguration {
|
|||||||
/// Base instructions override.
|
/// Base instructions override.
|
||||||
base_instructions: Option<String>,
|
base_instructions: Option<String>,
|
||||||
|
|
||||||
|
/// Compact prompt override.
|
||||||
|
compact_prompt: Option<String>,
|
||||||
|
|
||||||
/// When to escalate for approval for execution
|
/// When to escalate for approval for execution
|
||||||
approval_policy: AskForApproval,
|
approval_policy: AskForApproval,
|
||||||
/// How to sandbox commands executed in the system
|
/// How to sandbox commands executed in the system
|
||||||
@@ -407,6 +418,7 @@ impl Session {
|
|||||||
client,
|
client,
|
||||||
cwd: session_configuration.cwd.clone(),
|
cwd: session_configuration.cwd.clone(),
|
||||||
base_instructions: session_configuration.base_instructions.clone(),
|
base_instructions: session_configuration.base_instructions.clone(),
|
||||||
|
compact_prompt: session_configuration.compact_prompt.clone(),
|
||||||
user_instructions: session_configuration.user_instructions.clone(),
|
user_instructions: session_configuration.user_instructions.clone(),
|
||||||
approval_policy: session_configuration.approval_policy,
|
approval_policy: session_configuration.approval_policy,
|
||||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||||
@@ -1313,7 +1325,7 @@ mod handlers {
|
|||||||
use crate::codex::Session;
|
use crate::codex::Session;
|
||||||
use crate::codex::SessionSettingsUpdate;
|
use crate::codex::SessionSettingsUpdate;
|
||||||
use crate::codex::TurnContext;
|
use crate::codex::TurnContext;
|
||||||
use crate::codex::compact;
|
|
||||||
use crate::codex::spawn_review_thread;
|
use crate::codex::spawn_review_thread;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::mcp::auth::compute_auth_statuses;
|
use crate::mcp::auth::compute_auth_statuses;
|
||||||
@@ -1540,7 +1552,7 @@ mod handlers {
|
|||||||
// Attempt to inject input into current task
|
// Attempt to inject input into current task
|
||||||
if let Err(items) = sess
|
if let Err(items) = sess
|
||||||
.inject_input(vec![UserInput::Text {
|
.inject_input(vec![UserInput::Text {
|
||||||
text: compact::SUMMARIZATION_PROMPT.to_string(),
|
text: turn_context.compact_prompt().to_string(),
|
||||||
}])
|
}])
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -1664,6 +1676,7 @@ async fn spawn_review_thread(
|
|||||||
tools_config,
|
tools_config,
|
||||||
user_instructions: None,
|
user_instructions: None,
|
||||||
base_instructions: Some(base_instructions.clone()),
|
base_instructions: Some(base_instructions.clone()),
|
||||||
|
compact_prompt: parent_turn_context.compact_prompt.clone(),
|
||||||
approval_policy: parent_turn_context.approval_policy,
|
approval_policy: parent_turn_context.approval_policy,
|
||||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||||
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
|
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
|
||||||
@@ -2500,6 +2513,7 @@ mod tests {
|
|||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
user_instructions: config.user_instructions.clone(),
|
user_instructions: config.user_instructions.clone(),
|
||||||
base_instructions: config.base_instructions.clone(),
|
base_instructions: config.base_instructions.clone(),
|
||||||
|
compact_prompt: config.compact_prompt.clone(),
|
||||||
approval_policy: config.approval_policy,
|
approval_policy: config.approval_policy,
|
||||||
sandbox_policy: config.sandbox_policy.clone(),
|
sandbox_policy: config.sandbox_policy.clone(),
|
||||||
cwd: config.cwd.clone(),
|
cwd: config.cwd.clone(),
|
||||||
@@ -2574,6 +2588,7 @@ mod tests {
|
|||||||
model_reasoning_summary: config.model_reasoning_summary,
|
model_reasoning_summary: config.model_reasoning_summary,
|
||||||
user_instructions: config.user_instructions.clone(),
|
user_instructions: config.user_instructions.clone(),
|
||||||
base_instructions: config.base_instructions.clone(),
|
base_instructions: config.base_instructions.clone(),
|
||||||
|
compact_prompt: config.compact_prompt.clone(),
|
||||||
approval_policy: config.approval_policy,
|
approval_policy: config.approval_policy,
|
||||||
sandbox_policy: config.sandbox_policy.clone(),
|
sandbox_policy: config.sandbox_policy.clone(),
|
||||||
cwd: config.cwd.clone(),
|
cwd: config.cwd.clone(),
|
||||||
|
|||||||
@@ -39,9 +39,8 @@ pub(crate) async fn run_inline_auto_compact_task(
|
|||||||
sess: Arc<Session>,
|
sess: Arc<Session>,
|
||||||
turn_context: Arc<TurnContext>,
|
turn_context: Arc<TurnContext>,
|
||||||
) {
|
) {
|
||||||
let input = vec![UserInput::Text {
|
let prompt = turn_context.compact_prompt().to_string();
|
||||||
text: SUMMARIZATION_PROMPT.to_string(),
|
let input = vec![UserInput::Text { text: prompt }];
|
||||||
}];
|
|
||||||
run_compact_task_inner(sess, turn_context, input).await;
|
run_compact_task_inner(sess, turn_context, input).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ pub struct Config {
|
|||||||
/// Base instructions override.
|
/// Base instructions override.
|
||||||
pub base_instructions: Option<String>,
|
pub base_instructions: Option<String>,
|
||||||
|
|
||||||
|
/// Compact prompt override.
|
||||||
|
pub compact_prompt: Option<String>,
|
||||||
|
|
||||||
/// Optional external notifier command. When set, Codex will spawn this
|
/// Optional external notifier command. When set, Codex will spawn this
|
||||||
/// program after each completed *turn* (i.e. when the agent finishes
|
/// program after each completed *turn* (i.e. when the agent finishes
|
||||||
/// processing a user submission). The value must be the full command
|
/// processing a user submission). The value must be the full command
|
||||||
@@ -540,6 +543,8 @@ pub struct ConfigToml {
|
|||||||
|
|
||||||
/// System instructions.
|
/// System instructions.
|
||||||
pub instructions: Option<String>,
|
pub instructions: Option<String>,
|
||||||
|
/// Compact prompt used for history compaction.
|
||||||
|
pub compact_prompt: Option<String>,
|
||||||
|
|
||||||
/// When set, restricts ChatGPT login to a specific workspace identifier.
|
/// When set, restricts ChatGPT login to a specific workspace identifier.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -644,6 +649,7 @@ pub struct ConfigToml {
|
|||||||
|
|
||||||
/// Legacy, now use features
|
/// Legacy, now use features
|
||||||
pub experimental_instructions_file: Option<PathBuf>,
|
pub experimental_instructions_file: Option<PathBuf>,
|
||||||
|
pub experimental_compact_prompt_file: Option<PathBuf>,
|
||||||
pub experimental_use_exec_command_tool: Option<bool>,
|
pub experimental_use_exec_command_tool: Option<bool>,
|
||||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||||
pub experimental_use_rmcp_client: Option<bool>,
|
pub experimental_use_rmcp_client: Option<bool>,
|
||||||
@@ -824,6 +830,7 @@ pub struct ConfigOverrides {
|
|||||||
pub config_profile: Option<String>,
|
pub config_profile: Option<String>,
|
||||||
pub codex_linux_sandbox_exe: Option<PathBuf>,
|
pub codex_linux_sandbox_exe: Option<PathBuf>,
|
||||||
pub base_instructions: Option<String>,
|
pub base_instructions: Option<String>,
|
||||||
|
pub compact_prompt: Option<String>,
|
||||||
pub include_apply_patch_tool: Option<bool>,
|
pub include_apply_patch_tool: Option<bool>,
|
||||||
pub include_view_image_tool: Option<bool>,
|
pub include_view_image_tool: Option<bool>,
|
||||||
pub show_raw_agent_reasoning: Option<bool>,
|
pub show_raw_agent_reasoning: Option<bool>,
|
||||||
@@ -854,6 +861,7 @@ impl Config {
|
|||||||
config_profile: config_profile_key,
|
config_profile: config_profile_key,
|
||||||
codex_linux_sandbox_exe,
|
codex_linux_sandbox_exe,
|
||||||
base_instructions,
|
base_instructions,
|
||||||
|
compact_prompt,
|
||||||
include_apply_patch_tool: include_apply_patch_tool_override,
|
include_apply_patch_tool: include_apply_patch_tool_override,
|
||||||
include_view_image_tool: include_view_image_tool_override,
|
include_view_image_tool: include_view_image_tool_override,
|
||||||
show_raw_agent_reasoning,
|
show_raw_agent_reasoning,
|
||||||
@@ -1030,6 +1038,15 @@ impl Config {
|
|||||||
.and_then(|info| info.auto_compact_token_limit)
|
.and_then(|info| info.auto_compact_token_limit)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let compact_prompt = compact_prompt.or(cfg.compact_prompt).and_then(|value| {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(trimmed.to_string())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Load base instructions override from a file if specified. If the
|
// Load base instructions override from a file if specified. If the
|
||||||
// path is relative, resolve it against the effective cwd so the
|
// path is relative, resolve it against the effective cwd so the
|
||||||
// behaviour matches other path-like config values.
|
// behaviour matches other path-like config values.
|
||||||
@@ -1037,10 +1054,24 @@ impl Config {
|
|||||||
.experimental_instructions_file
|
.experimental_instructions_file
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or(cfg.experimental_instructions_file.as_ref());
|
.or(cfg.experimental_instructions_file.as_ref());
|
||||||
let file_base_instructions =
|
let file_base_instructions = Self::load_override_from_file(
|
||||||
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
|
experimental_instructions_path,
|
||||||
|
&resolved_cwd,
|
||||||
|
"experimental instructions file",
|
||||||
|
)?;
|
||||||
let base_instructions = base_instructions.or(file_base_instructions);
|
let base_instructions = base_instructions.or(file_base_instructions);
|
||||||
|
|
||||||
|
let experimental_compact_prompt_path = config_profile
|
||||||
|
.experimental_compact_prompt_file
|
||||||
|
.as_ref()
|
||||||
|
.or(cfg.experimental_compact_prompt_file.as_ref());
|
||||||
|
let file_compact_prompt = Self::load_override_from_file(
|
||||||
|
experimental_compact_prompt_path,
|
||||||
|
&resolved_cwd,
|
||||||
|
"experimental compact prompt file",
|
||||||
|
)?;
|
||||||
|
let compact_prompt = compact_prompt.or(file_compact_prompt);
|
||||||
|
|
||||||
// Default review model when not set in config; allow CLI override to take precedence.
|
// Default review model when not set in config; allow CLI override to take precedence.
|
||||||
let review_model = override_review_model
|
let review_model = override_review_model
|
||||||
.or(cfg.review_model)
|
.or(cfg.review_model)
|
||||||
@@ -1064,6 +1095,7 @@ impl Config {
|
|||||||
notify: cfg.notify,
|
notify: cfg.notify,
|
||||||
user_instructions,
|
user_instructions,
|
||||||
base_instructions,
|
base_instructions,
|
||||||
|
compact_prompt,
|
||||||
// The config.toml omits "_mode" because it's a config file. However, "_mode"
|
// The config.toml omits "_mode" because it's a config file. However, "_mode"
|
||||||
// is important in code to differentiate the mode from the store implementation.
|
// is important in code to differentiate the mode from the store implementation.
|
||||||
cli_auth_credentials_store_mode: cfg.cli_auth_credentials_store.unwrap_or_default(),
|
cli_auth_credentials_store_mode: cfg.cli_auth_credentials_store.unwrap_or_default(),
|
||||||
@@ -1160,18 +1192,15 @@ impl Config {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_base_instructions(
|
fn load_override_from_file(
|
||||||
path: Option<&PathBuf>,
|
path: Option<&PathBuf>,
|
||||||
cwd: &Path,
|
cwd: &Path,
|
||||||
|
description: &str,
|
||||||
) -> std::io::Result<Option<String>> {
|
) -> std::io::Result<Option<String>> {
|
||||||
let p = match path.as_ref() {
|
let Some(p) = path else {
|
||||||
None => return Ok(None),
|
return Ok(None);
|
||||||
Some(p) => p,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve relative paths against the provided cwd to make CLI
|
|
||||||
// overrides consistent regardless of where the process was launched
|
|
||||||
// from.
|
|
||||||
let full_path = if p.is_relative() {
|
let full_path = if p.is_relative() {
|
||||||
cwd.join(p)
|
cwd.join(p)
|
||||||
} else {
|
} else {
|
||||||
@@ -1181,10 +1210,7 @@ impl Config {
|
|||||||
let contents = std::fs::read_to_string(&full_path).map_err(|e| {
|
let contents = std::fs::read_to_string(&full_path).map_err(|e| {
|
||||||
std::io::Error::new(
|
std::io::Error::new(
|
||||||
e.kind(),
|
e.kind(),
|
||||||
format!(
|
format!("failed to read {description} {}: {e}", full_path.display()),
|
||||||
"failed to read experimental instructions file {}: {e}",
|
|
||||||
full_path.display()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -1192,10 +1218,7 @@ impl Config {
|
|||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
Err(std::io::Error::new(
|
Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::InvalidData,
|
std::io::ErrorKind::InvalidData,
|
||||||
format!(
|
format!("{description} is empty: {}", full_path.display()),
|
||||||
"experimental instructions file is empty: {}",
|
|
||||||
full_path.display()
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(Some(s))
|
Ok(Some(s))
|
||||||
@@ -2653,6 +2676,61 @@ model = "gpt-5-codex"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_override_sets_compact_prompt() -> std::io::Result<()> {
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
let overrides = ConfigOverrides {
|
||||||
|
compact_prompt: Some("Use the compact override".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Config::load_from_base_config_with_overrides(
|
||||||
|
ConfigToml::default(),
|
||||||
|
overrides,
|
||||||
|
codex_home.path().to_path_buf(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.compact_prompt.as_deref(),
|
||||||
|
Some("Use the compact override")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_compact_prompt_from_file() -> std::io::Result<()> {
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
let workspace = codex_home.path().join("workspace");
|
||||||
|
std::fs::create_dir_all(&workspace)?;
|
||||||
|
|
||||||
|
let prompt_path = workspace.join("compact_prompt.txt");
|
||||||
|
std::fs::write(&prompt_path, " summarize differently ")?;
|
||||||
|
|
||||||
|
let cfg = ConfigToml {
|
||||||
|
experimental_compact_prompt_file: Some(PathBuf::from("compact_prompt.txt")),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let overrides = ConfigOverrides {
|
||||||
|
cwd: Some(workspace),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Config::load_from_base_config_with_overrides(
|
||||||
|
cfg,
|
||||||
|
overrides,
|
||||||
|
codex_home.path().to_path_buf(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.compact_prompt.as_deref(),
|
||||||
|
Some("summarize differently")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn create_test_fixture() -> std::io::Result<PrecedenceTestFixture> {
|
fn create_test_fixture() -> std::io::Result<PrecedenceTestFixture> {
|
||||||
let toml = r#"
|
let toml = r#"
|
||||||
model = "o3"
|
model = "o3"
|
||||||
@@ -2808,6 +2886,7 @@ model_verbosity = "high"
|
|||||||
model_verbosity: None,
|
model_verbosity: None,
|
||||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
forced_chatgpt_workspace_id: None,
|
forced_chatgpt_workspace_id: None,
|
||||||
forced_login_method: None,
|
forced_login_method: None,
|
||||||
include_apply_patch_tool: false,
|
include_apply_patch_tool: false,
|
||||||
@@ -2879,6 +2958,7 @@ model_verbosity = "high"
|
|||||||
model_verbosity: None,
|
model_verbosity: None,
|
||||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
forced_chatgpt_workspace_id: None,
|
forced_chatgpt_workspace_id: None,
|
||||||
forced_login_method: None,
|
forced_login_method: None,
|
||||||
include_apply_patch_tool: false,
|
include_apply_patch_tool: false,
|
||||||
@@ -2965,6 +3045,7 @@ model_verbosity = "high"
|
|||||||
model_verbosity: None,
|
model_verbosity: None,
|
||||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
forced_chatgpt_workspace_id: None,
|
forced_chatgpt_workspace_id: None,
|
||||||
forced_login_method: None,
|
forced_login_method: None,
|
||||||
include_apply_patch_tool: false,
|
include_apply_patch_tool: false,
|
||||||
@@ -3037,6 +3118,7 @@ model_verbosity = "high"
|
|||||||
model_verbosity: Some(Verbosity::High),
|
model_verbosity: Some(Verbosity::High),
|
||||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
forced_chatgpt_workspace_id: None,
|
forced_chatgpt_workspace_id: None,
|
||||||
forced_login_method: None,
|
forced_login_method: None,
|
||||||
include_apply_patch_tool: false,
|
include_apply_patch_tool: false,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub struct ConfigProfile {
|
|||||||
pub model_verbosity: Option<Verbosity>,
|
pub model_verbosity: Option<Verbosity>,
|
||||||
pub chatgpt_base_url: Option<String>,
|
pub chatgpt_base_url: Option<String>,
|
||||||
pub experimental_instructions_file: Option<PathBuf>,
|
pub experimental_instructions_file: Option<PathBuf>,
|
||||||
|
pub experimental_compact_prompt_file: Option<PathBuf>,
|
||||||
pub include_apply_patch_tool: Option<bool>,
|
pub include_apply_patch_tool: Option<bool>,
|
||||||
pub include_view_image_tool: Option<bool>,
|
pub include_view_image_tool: Option<bool>,
|
||||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||||
|
|||||||
@@ -261,6 +261,65 @@ async fn summarize_context_three_requests_and_instructions() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn manual_compact_uses_custom_prompt() {
|
||||||
|
skip_if_no_network!();
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let sse_stream = sse(vec![ev_completed("r1")]);
|
||||||
|
mount_sse_once(&server, sse_stream).await;
|
||||||
|
|
||||||
|
let custom_prompt = "Use this compact prompt instead";
|
||||||
|
|
||||||
|
let model_provider = ModelProviderInfo {
|
||||||
|
base_url: Some(format!("{}/v1", server.uri())),
|
||||||
|
..built_in_model_providers()["openai"].clone()
|
||||||
|
};
|
||||||
|
let home = TempDir::new().unwrap();
|
||||||
|
let mut config = load_default_config_for_test(&home);
|
||||||
|
config.model_provider = model_provider;
|
||||||
|
config.compact_prompt = Some(custom_prompt.to_string());
|
||||||
|
|
||||||
|
let conversation_manager = ConversationManager::with_auth(CodexAuth::from_api_key("dummy"));
|
||||||
|
let codex = conversation_manager
|
||||||
|
.new_conversation(config)
|
||||||
|
.await
|
||||||
|
.expect("create conversation")
|
||||||
|
.conversation;
|
||||||
|
|
||||||
|
codex.submit(Op::Compact).await.expect("trigger compact");
|
||||||
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
|
||||||
|
|
||||||
|
let requests = server.received_requests().await.expect("collect requests");
|
||||||
|
let body = requests
|
||||||
|
.iter()
|
||||||
|
.find_map(|req| req.body_json::<serde_json::Value>().ok())
|
||||||
|
.expect("summary request body");
|
||||||
|
|
||||||
|
let input = body
|
||||||
|
.get("input")
|
||||||
|
.and_then(|v| v.as_array())
|
||||||
|
.expect("input array");
|
||||||
|
let mut found_custom_prompt = false;
|
||||||
|
let mut found_default_prompt = false;
|
||||||
|
|
||||||
|
for item in input {
|
||||||
|
if item["type"].as_str() != Some("message") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let text = item["content"][0]["text"].as_str().unwrap_or_default();
|
||||||
|
if text == custom_prompt {
|
||||||
|
found_custom_prompt = true;
|
||||||
|
}
|
||||||
|
if text == SUMMARIZATION_PROMPT {
|
||||||
|
found_default_prompt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(found_custom_prompt, "custom prompt should be injected");
|
||||||
|
assert!(!found_default_prompt, "default prompt should be replaced");
|
||||||
|
}
|
||||||
|
|
||||||
// Windows CI only: bump to 4 workers to prevent SSE/event starvation and test timeouts.
|
// Windows CI only: bump to 4 workers to prevent SSE/event starvation and test timeouts.
|
||||||
#[cfg_attr(windows, tokio::test(flavor = "multi_thread", worker_threads = 4))]
|
#[cfg_attr(windows, tokio::test(flavor = "multi_thread", worker_threads = 4))]
|
||||||
#[cfg_attr(not(windows), tokio::test(flavor = "multi_thread", worker_threads = 2))]
|
#[cfg_attr(not(windows), tokio::test(flavor = "multi_thread", worker_threads = 2))]
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ Request `newConversation` params (subset):
|
|||||||
- `sandbox`: `read-only` | `workspace-write` | `danger-full-access`
|
- `sandbox`: `read-only` | `workspace-write` | `danger-full-access`
|
||||||
- `config`: map of additional config overrides
|
- `config`: map of additional config overrides
|
||||||
- `baseInstructions`: optional instruction override
|
- `baseInstructions`: optional instruction override
|
||||||
|
- `compactPrompt`: optional replacement for the default compaction prompt
|
||||||
- `includePlanTool` / `includeApplyPatchTool`: booleans
|
- `includePlanTool` / `includeApplyPatchTool`: booleans
|
||||||
|
|
||||||
Response: `{ conversationId, model, reasoningEffort?, rolloutPath }`
|
Response: `{ conversationId, model, reasoningEffort?, rolloutPath }`
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
|||||||
model_provider,
|
model_provider,
|
||||||
codex_linux_sandbox_exe,
|
codex_linux_sandbox_exe,
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
include_apply_patch_tool: None,
|
include_apply_patch_tool: None,
|
||||||
include_view_image_tool: None,
|
include_view_image_tool: None,
|
||||||
show_raw_agent_reasoning: oss.then_some(true),
|
show_raw_agent_reasoning: oss.then_some(true),
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ pub struct CodexToolCallParam {
|
|||||||
/// The set of instructions to use instead of the default ones.
|
/// The set of instructions to use instead of the default ones.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_instructions: Option<String>,
|
pub base_instructions: Option<String>,
|
||||||
|
|
||||||
|
/// Prompt used when compacting the conversation.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub compact_prompt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Custom enum mirroring [`AskForApproval`], but has an extra dependency on
|
/// Custom enum mirroring [`AskForApproval`], but has an extra dependency on
|
||||||
@@ -141,6 +145,7 @@ impl CodexToolCallParam {
|
|||||||
sandbox,
|
sandbox,
|
||||||
config: cli_overrides,
|
config: cli_overrides,
|
||||||
base_instructions,
|
base_instructions,
|
||||||
|
compact_prompt,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Build the `ConfigOverrides` recognized by codex-core.
|
// Build the `ConfigOverrides` recognized by codex-core.
|
||||||
@@ -154,6 +159,7 @@ impl CodexToolCallParam {
|
|||||||
model_provider: None,
|
model_provider: None,
|
||||||
codex_linux_sandbox_exe,
|
codex_linux_sandbox_exe,
|
||||||
base_instructions,
|
base_instructions,
|
||||||
|
compact_prompt,
|
||||||
include_apply_patch_tool: None,
|
include_apply_patch_tool: None,
|
||||||
include_view_image_tool: None,
|
include_view_image_tool: None,
|
||||||
show_raw_agent_reasoning: None,
|
show_raw_agent_reasoning: None,
|
||||||
@@ -288,6 +294,10 @@ mod tests {
|
|||||||
"description": "The set of instructions to use instead of the default ones.",
|
"description": "The set of instructions to use instead of the default ones.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"compact-prompt": {
|
||||||
|
"description": "Prompt used when compacting the conversation.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"prompt"
|
"prompt"
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ pub async fn run_main(
|
|||||||
config_profile: cli.config_profile.clone(),
|
config_profile: cli.config_profile.clone(),
|
||||||
codex_linux_sandbox_exe,
|
codex_linux_sandbox_exe,
|
||||||
base_instructions: None,
|
base_instructions: None,
|
||||||
|
compact_prompt: None,
|
||||||
include_apply_patch_tool: None,
|
include_apply_patch_tool: None,
|
||||||
include_view_image_tool: None,
|
include_view_image_tool: None,
|
||||||
show_raw_agent_reasoning: cli.oss.then_some(true),
|
show_raw_agent_reasoning: cli.oss.then_some(true),
|
||||||
|
|||||||
Reference in New Issue
Block a user