Persist collaboration mode masks in history

This commit is contained in:
Charles Cunningham
2026-01-30 14:35:21 -08:00
parent 864d322ae7
commit f86b1de650
6 changed files with 58 additions and 6 deletions

View File

@@ -69,7 +69,9 @@ impl<'a> ChatRequestBuilder<'a> {
last_emitted_role = Some("assistant")
}
ResponseItem::FunctionCallOutput { .. } => last_emitted_role = Some("tool"),
ResponseItem::Reasoning { .. } | ResponseItem::Other => {}
ResponseItem::Reasoning { .. }
| ResponseItem::CollaborationModeUpdate { .. }
| ResponseItem::Other => {}
ResponseItem::CustomToolCall { .. } => {}
ResponseItem::CustomToolCallOutput { .. } => {}
ResponseItem::WebSearchCall { .. } => {}
@@ -284,6 +286,7 @@ impl<'a> ChatRequestBuilder<'a> {
}
ResponseItem::Reasoning { .. }
| ResponseItem::WebSearchCall { .. }
| ResponseItem::CollaborationModeUpdate { .. }
| ResponseItem::Other
| ResponseItem::Compaction { .. } => {
continue;

View File

@@ -40,7 +40,6 @@ use async_channel::Receiver;
use async_channel::Sender;
use codex_protocol::ThreadId;
use codex_protocol::approvals::ExecPolicyAmendment;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::dynamic_tools::DynamicToolResponse;
@@ -203,6 +202,8 @@ use crate::windows_sandbox::WindowsSandboxLevelExt;
use codex_async_utils::OrCancelExt;
use codex_otel::OtelManager;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::config_types::WindowsSandboxLevel;
@@ -1365,6 +1366,26 @@ impl Session {
}
}
fn collaboration_mode_mask_from_mode(mode: &CollaborationMode) -> CollaborationModeMask {
CollaborationModeMask {
name: Self::collaboration_mode_name(mode.mode).to_string(),
mode: Some(mode.mode),
model: Some(mode.model().to_string()),
reasoning_effort: Some(mode.reasoning_effort()),
developer_instructions: Some(mode.settings.developer_instructions.clone()),
}
}
fn collaboration_mode_name(kind: ModeKind) -> &'static str {
match kind {
ModeKind::Plan => "Plan",
ModeKind::Code => "Code",
ModeKind::PairProgramming => "Pair Programming",
ModeKind::Execute => "Execute",
ModeKind::Custom => "Custom",
}
}
fn build_settings_update_items(
&self,
previous_context: Option<&Arc<TurnContext>>,
@@ -1383,6 +1404,13 @@ impl Session {
{
update_items.push(permissions_item);
}
if let Some(next_mode) = next_collaboration_mode {
if previous_collaboration_mode != next_mode {
update_items.push(ResponseItem::CollaborationModeUpdate {
mask: Self::collaboration_mode_mask_from_mode(next_mode),
});
}
}
if let Some(collaboration_mode_item) = self.build_collaboration_mode_update_item(
previous_collaboration_mode,
next_collaboration_mode,
@@ -1891,6 +1919,9 @@ impl Session {
state.session_configuration.base_instructions.clone(),
)
};
items.push(ResponseItem::CollaborationModeUpdate {
mask: Self::collaboration_mode_mask_from_mode(&collaboration_mode),
});
if let Some(collab_instructions) =
DeveloperInstructions::from_collaboration_mode(&collaboration_mode)
{
@@ -3120,6 +3151,9 @@ mod handlers {
fn last_collaboration_mask(items: &[ResponseItem]) -> Option<CollaborationModeMask> {
items.iter().rev().find_map(|item| {
if let ResponseItem::CollaborationModeUpdate { mask } = item {
return Some(mask.clone());
}
let ResponseItem::Message { role, content, .. } = item else {
return None;
};

View File

@@ -59,7 +59,9 @@ impl ContextManager {
for item in items {
let item_ref = item.deref();
let is_ghost_snapshot = matches!(item_ref, ResponseItem::GhostSnapshot { .. });
if !is_api_message(item_ref) && !is_ghost_snapshot {
let is_collaboration_mode_update =
matches!(item_ref, ResponseItem::CollaborationModeUpdate { .. });
if !is_api_message(item_ref) && !is_ghost_snapshot && !is_collaboration_mode_update {
continue;
}
@@ -72,8 +74,12 @@ impl ContextManager {
/// normalization and drop un-suited items.
pub(crate) fn for_prompt(mut self) -> Vec<ResponseItem> {
self.normalize_history();
self.items
.retain(|item| !matches!(item, ResponseItem::GhostSnapshot { .. }));
self.items.retain(|item| {
!matches!(
item,
ResponseItem::GhostSnapshot { .. } | ResponseItem::CollaborationModeUpdate { .. }
)
});
self.items
}
@@ -94,7 +100,8 @@ impl ContextManager {
let items_tokens = self.items.iter().fold(0i64, |acc, item| {
acc + match item {
ResponseItem::GhostSnapshot { .. } => 0,
ResponseItem::GhostSnapshot { .. }
| ResponseItem::CollaborationModeUpdate { .. } => 0,
ResponseItem::Reasoning {
encrypted_content: Some(content),
..
@@ -301,6 +308,7 @@ impl ContextManager {
| ResponseItem::CustomToolCall { .. }
| ResponseItem::Compaction { .. }
| ResponseItem::GhostSnapshot { .. }
| ResponseItem::CollaborationModeUpdate { .. }
| ResponseItem::Other => item.clone(),
}
}
@@ -320,6 +328,7 @@ fn is_api_message(message: &ResponseItem) -> bool {
| ResponseItem::WebSearchCall { .. }
| ResponseItem::Compaction { .. } => true,
ResponseItem::GhostSnapshot { .. } => false,
ResponseItem::CollaborationModeUpdate { .. } => false,
ResponseItem::Other => false,
}
}

View File

@@ -27,6 +27,7 @@ pub(crate) fn should_persist_response_item(item: &ResponseItem) -> bool {
| ResponseItem::CustomToolCall { .. }
| ResponseItem::CustomToolCallOutput { .. }
| ResponseItem::WebSearchCall { .. }
| ResponseItem::CollaborationModeUpdate { .. }
| ResponseItem::GhostSnapshot { .. }
| ResponseItem::Compaction { .. } => true,
ResponseItem::Other => false,

View File

@@ -545,6 +545,7 @@ impl OtelManager {
ResponseItem::CustomToolCall { .. } => "custom_tool_call".into(),
ResponseItem::CustomToolCallOutput { .. } => "custom_tool_call_output".into(),
ResponseItem::WebSearchCall { .. } => "web_search_call".into(),
ResponseItem::CollaborationModeUpdate { .. } => "collaboration_mode_update".into(),
ResponseItem::GhostSnapshot { .. } => "ghost_snapshot".into(),
ResponseItem::Compaction { .. } => "compaction".into(),
ResponseItem::Other => "other".into(),

View File

@@ -11,6 +11,7 @@ use serde::ser::Serializer;
use ts_rs::TS;
use crate::config_types::CollaborationMode;
use crate::config_types::CollaborationModeMask;
use crate::config_types::SandboxMode;
use crate::protocol::AskForApproval;
use crate::protocol::COLLABORATION_MODE_CLOSE_TAG;
@@ -86,6 +87,9 @@ pub enum ResponseItem {
#[ts(optional)]
end_turn: Option<bool>,
},
CollaborationModeUpdate {
mask: CollaborationModeMask,
},
Reasoning {
#[serde(default, skip_serializing)]
#[ts(skip)]