Merge remote-tracking branch 'origin/main' into codex/interrupt-hook-contract-tightening

This commit is contained in:
Andrei Eternal
2026-06-03 15:11:17 -07:00
730 changed files with 31143 additions and 8497 deletions
+1
View File
@@ -35,6 +35,7 @@ fn rate_limit_snapshot() -> RateLimitSnapshot {
resets_at: Some(secondary_reset_at),
}),
credits: None,
individual_limit: None,
plan_type: None,
rate_limit_reached_type: None,
}
+22 -14
View File
@@ -374,16 +374,9 @@ impl Default for PermissionProfile {
impl PermissionProfile {
/// Managed read-only filesystem access with restricted network access.
pub fn read_only() -> Self {
let file_system = FileSystemSandboxPolicy::read_only();
Self::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}],
glob_scan_max_depth: None,
},
file_system: ManagedFileSystemPermissions::from_sandbox_policy(&file_system),
network: NetworkSandboxPolicy::Restricted,
}
}
@@ -1026,9 +1019,10 @@ pub fn local_image_label_text(label_number: usize) -> String {
format!("[Image #{label_number}]")
}
pub fn local_image_open_tag_text(label_number: usize) -> String {
pub fn local_image_open_tag_text_with_path(label_number: usize, path: &std::path::Path) -> String {
let label = local_image_label_text(label_number);
format!("{LOCAL_IMAGE_OPEN_TAG_PREFIX}{label}{LOCAL_IMAGE_OPEN_TAG_SUFFIX}")
let path = path.display();
format!("{LOCAL_IMAGE_OPEN_TAG_PREFIX}{label} path=\"{path}\"{LOCAL_IMAGE_OPEN_TAG_SUFFIX}")
}
pub fn is_local_image_open_tag_text(text: &str) -> bool {
@@ -1087,7 +1081,7 @@ pub fn local_image_content_items_with_label_number(
let mut items = Vec::with_capacity(3);
if let Some(label_number) = label_number {
items.push(ContentItem::InputText {
text: local_image_open_tag_text(label_number),
text: local_image_open_tag_text_with_path(label_number, path),
});
}
items.push(ContentItem::InputImage {
@@ -2873,7 +2867,7 @@ mod tests {
detail: None,
},
UserInput::LocalImage {
path: local_path,
path: local_path.clone(),
detail: None,
},
]);
@@ -2890,7 +2884,10 @@ mod tests {
assert_eq!(
content.get(1),
Some(&ContentItem::InputText {
text: local_image_open_tag_text(/*label_number*/ 2),
text: local_image_open_tag_text_with_path(
/*label_number*/ 2,
&local_path
),
})
);
assert!(matches!(
@@ -2910,6 +2907,17 @@ mod tests {
Ok(())
}
#[test]
fn local_image_open_tag_preserves_path() {
assert_eq!(
local_image_open_tag_text_with_path(
/*label_number*/ 1,
std::path::Path::new(r#"/tmp/a&"<b>.png"#),
),
r#"<image name=[Image #1] path="/tmp/a&"<b>.png">"#
);
}
#[test]
fn local_image_user_input_preserves_requested_detail() -> Result<()> {
let dir = tempdir()?;
+28
View File
@@ -22,6 +22,7 @@ use crate::config_types::ReasoningSummary;
use crate::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use crate::config_types::ServiceTier;
use crate::config_types::Verbosity;
use crate::protocol::MultiAgentVersion;
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
pub const SPEED_TIER_FAST: &str = "fast";
@@ -339,12 +340,20 @@ pub struct ModelInfo {
pub used_fallback_model_metadata: bool,
#[serde(default)]
pub supports_search_tool: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_review_model_override: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_optional_model_selector"
)]
pub tool_mode: Option<ToolMode>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_optional_model_selector"
)]
pub multi_agent_version: Option<MultiAgentVersion>,
}
impl ModelInfo {
@@ -639,7 +648,9 @@ mod tests {
input_modalities: default_input_modalities(),
used_fallback_model_metadata: false,
supports_search_tool: false,
auto_review_model_override: None,
tool_mode: None,
multi_agent_version: None,
}
}
@@ -857,6 +868,7 @@ mod tests {
assert!(!model.supports_image_detail_original);
assert_eq!(model.web_search_tool_type, WebSearchToolType::Text);
assert!(!model.supports_search_tool);
assert_eq!(model.auto_review_model_override, None);
assert_eq!(model.tool_mode, None);
}
@@ -897,6 +909,22 @@ mod tests {
assert!(!object.contains_key("tool_mode"));
}
#[test]
fn model_info_treats_unknown_multi_agent_version_as_omitted() {
let mut value =
serde_json::to_value(test_model(/*spec*/ None)).expect("serialize test model");
let object = value
.as_object_mut()
.expect("model info should be an object");
object.insert(
"multi_agent_version".to_string(),
serde_json::Value::String("future_multi_agent_version".to_string()),
);
let model = serde_json::from_value::<ModelInfo>(value).expect("deserialize model info");
assert_eq!(model.multi_agent_version, None);
}
#[test]
fn resolved_context_window_prefers_context_window() {
let model = ModelInfo {
+14 -10
View File
@@ -358,22 +358,26 @@ pub fn project_roots_glob_pattern(subpath: &Path) -> String {
format!("{PROJECT_ROOTS_GLOB_PATTERN_PREFIX}{}", subpath.display())
}
fn read_only_file_system_entries() -> Vec<FileSystemSandboxEntry> {
vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}]
}
impl Default for FileSystemSandboxPolicy {
fn default() -> Self {
Self {
kind: FileSystemSandboxKind::Restricted,
glob_scan_max_depth: None,
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}],
}
Self::read_only()
}
}
impl FileSystemSandboxPolicy {
pub fn read_only() -> Self {
Self::restricted(read_only_file_system_entries())
}
pub fn unrestricted() -> Self {
Self {
kind: FileSystemSandboxKind::Unrestricted,
+110 -2
View File
@@ -1398,6 +1398,7 @@ pub enum HookSource {
SessionFlags,
Plugin,
CloudRequirements,
CloudManagedConfig,
LegacyManagedConfigFile,
LegacyManagedConfigMdm,
#[default]
@@ -2009,6 +2010,7 @@ pub struct RateLimitSnapshot {
pub primary: Option<RateLimitWindow>,
pub secondary: Option<RateLimitWindow>,
pub credits: Option<CreditsSnapshot>,
pub individual_limit: Option<SpendControlLimitSnapshot>,
pub plan_type: Option<crate::account::PlanType>,
pub rate_limit_reached_type: Option<RateLimitReachedType>,
}
@@ -2058,6 +2060,14 @@ pub struct CreditsSnapshot {
pub balance: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct SpendControlLimitSnapshot {
pub limit: String,
pub used: String,
pub remaining_percent: i32,
pub resets_at: i64,
}
// Includes prompts, tools and space to call compact.
const BASELINE_TOKENS: i64 = 12000;
@@ -2181,8 +2191,9 @@ pub struct UserMessageEvent {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub image_details: Vec<Option<ImageDetail>>,
/// Local file paths sourced from `UserInput::LocalImage`. These are kept so
/// the UI can reattach images when editing history, and should not be sent
/// to the model or treated as API-ready URLs.
/// the UI can reattach images when editing history. Local image prompts may
/// include a display form of the path, but these should not be treated as
/// API-ready URLs.
#[serde(default)]
pub local_images: Vec<std::path::PathBuf>,
/// Detail hints for `local_images`, indexed in parallel. Missing entries
@@ -2441,6 +2452,18 @@ impl InitialHistory {
}
}
pub fn get_multi_agent_version(&self) -> Option<MultiAgentVersion> {
match self {
InitialHistory::New | InitialHistory::Cleared => None,
InitialHistory::Resumed(resumed) => {
multi_agent_version_from_items(&resumed.history, Some(resumed.conversation_id))
}
InitialHistory::Forked(items) => {
multi_agent_version_from_items(items, /*thread_id*/ None)
}
}
}
pub fn get_resumed_session_sources(&self) -> Option<(SessionSource, Option<ThreadSource>)> {
let meta = self.get_resumed_session_meta()?;
Some((meta.source.clone(), meta.thread_source))
@@ -2451,6 +2474,11 @@ impl InitialHistory {
.and_then(|meta| meta.thread_source)
}
pub fn get_resumed_parent_thread_id(&self) -> Option<ThreadId> {
self.get_resumed_session_meta()
.and_then(|meta| meta.parent_thread_id)
}
fn get_resumed_session_meta(&self) -> Option<&SessionMeta> {
match self {
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => None,
@@ -2706,6 +2734,39 @@ impl fmt::Display for InternalSessionSource {
}
}
fn multi_agent_version_from_items(
items: &[RolloutItem],
thread_id: Option<ThreadId>,
) -> Option<MultiAgentVersion> {
let session_meta_version = items.iter().rev().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line)
if thread_id.is_none_or(|thread_id| meta_line.meta.id == thread_id) =>
{
meta_line.meta.multi_agent_version
}
_ => None,
});
session_meta_version.or_else(|| {
items.iter().rev().find_map(|item| match item {
RolloutItem::TurnContext(turn_context) => turn_context.multi_agent_version,
RolloutItem::SessionMeta(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::EventMsg(_) => None,
})
})
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
pub enum MultiAgentVersion {
Disabled,
V1,
V2,
}
/// SessionMeta contains session-level data that doesn't correspond to a specific turn.
///
/// NOTE: There used to be an `instructions` field here, which stored user_instructions, but we
@@ -2716,6 +2777,8 @@ pub struct SessionMeta {
pub id: ThreadId,
#[serde(skip_serializing_if = "Option::is_none")]
pub forked_from_id: Option<ThreadId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_thread_id: Option<ThreadId>,
pub timestamp: String,
pub cwd: PathBuf,
pub originator: String,
@@ -2743,6 +2806,8 @@ pub struct SessionMeta {
pub dynamic_tools: Option<Vec<DynamicToolSpec>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi_agent_version: Option<MultiAgentVersion>,
}
impl Default for SessionMeta {
@@ -2750,6 +2815,7 @@ impl Default for SessionMeta {
SessionMeta {
id: ThreadId::default(),
forked_from_id: None,
parent_thread_id: None,
timestamp: String::new(),
cwd: PathBuf::new(),
originator: String::new(),
@@ -2763,6 +2829,7 @@ impl Default for SessionMeta {
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
multi_agent_version: None,
}
}
}
@@ -2842,6 +2909,8 @@ pub struct TurnContextItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub collaboration_mode: Option<CollaborationMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multi_agent_version: Option<MultiAgentVersion>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub realtime_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffortConfig>,
@@ -3421,6 +3490,8 @@ pub struct SessionConfiguredEvent {
pub thread_id: ThreadId,
#[serde(skip_serializing_if = "Option::is_none")]
pub forked_from_id: Option<ThreadId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_thread_id: Option<ThreadId>,
/// Optional analytics source classification for this thread.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thread_source: Option<ThreadSource>,
@@ -3490,6 +3561,7 @@ impl<'de> Deserialize<'de> for SessionConfiguredEvent {
#[serde(default)]
thread_id: Option<ThreadId>,
forked_from_id: Option<ThreadId>,
parent_thread_id: Option<ThreadId>,
#[serde(default)]
thread_source: Option<ThreadSource>,
#[serde(default)]
@@ -3530,6 +3602,7 @@ impl<'de> Deserialize<'de> for SessionConfiguredEvent {
session_id: wire.session_id,
thread_id: wire.thread_id.unwrap_or_else(|| wire.session_id.into()),
forked_from_id: wire.forked_from_id,
parent_thread_id: wire.parent_thread_id,
thread_source: wire.thread_source,
thread_name: wire.thread_name,
model: wire.model,
@@ -5259,6 +5332,39 @@ mod tests {
Ok(())
}
#[test]
fn multi_agent_version_uses_newest_present_session_meta_value() -> Result<()> {
let thread_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
let older_meta = SessionMetaLine {
meta: SessionMeta {
id: thread_id,
multi_agent_version: Some(MultiAgentVersion::V2),
..Default::default()
},
git: None,
};
let newer_meta_without_version = SessionMetaLine {
meta: SessionMeta {
id: thread_id,
multi_agent_version: None,
..Default::default()
},
git: None,
};
assert_eq!(
multi_agent_version_from_items(
&[
RolloutItem::SessionMeta(older_meta),
RolloutItem::SessionMeta(newer_meta_without_version),
],
Some(thread_id),
),
Some(MultiAgentVersion::V2)
);
Ok(())
}
#[test]
fn turn_context_item_serializes_network_when_present() -> Result<()> {
let item = TurnContextItem {
@@ -5285,6 +5391,7 @@ mod tests {
model: "gpt-5".to_string(),
personality: None,
collaboration_mode: None,
multi_agent_version: None,
realtime_active: None,
effort: None,
summary: ReasoningSummaryConfig::Auto,
@@ -5329,6 +5436,7 @@ mod tests {
session_id,
thread_id,
forked_from_id: None,
parent_thread_id: None,
thread_source: None,
thread_name: None,
model: "codex-mini-latest".to_string(),
@@ -48,6 +48,14 @@ impl From<AdditionalPermissionProfile> for RequestPermissionProfile {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestPermissionsArgs {
#[serde(
default,
rename = "environment_id",
alias = "environmentId",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub environment_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
pub permissions: RequestPermissionProfile,
@@ -71,6 +79,15 @@ pub struct RequestPermissionsEvent {
/// Uses `#[serde(default)]` for backwards compatibility.
#[serde(default)]
pub turn_id: String,
#[serde(
default,
rename = "environmentId",
alias = "environment_id",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
#[ts(rename = "environmentId")]
pub environment_id: Option<String>,
#[ts(type = "number")]
pub started_at_ms: i64,
#[serde(skip_serializing_if = "Option::is_none")]