feat(sync): backport vital protocol and model parity updates

This commit is contained in:
James Peter
2026-02-27 03:32:57 +10:00
parent 158d9b9506
commit 7e53c5785e
9 changed files with 261 additions and 36 deletions
+3 -1
View File
@@ -3570,6 +3570,7 @@ async fn handle_response_item(
timeout_ms: action.timeout_ms,
sandbox_permissions: None,
prefix_rule: None,
additional_permissions: None,
justification: None,
};
let effective_call_id = match (call_id, id) {
@@ -6838,7 +6839,8 @@ fn parse_container_exec_arguments(
) -> Result<ExecParams, Box<ResponseInputItem>> {
// Parse command.
//
// Newer prompts use `sandbox_permissions` ("use_default" | "require_escalated");
// Newer prompts use `sandbox_permissions` ("use_default" |
// "with_additional_permissions" | "require_escalated");
// older ones used `with_escalated_permissions: bool`. Accept both.
let parsed: std::result::Result<serde_json::Value, serde_json::Error> =
serde_json::from_str(&arguments);
+3 -3
View File
@@ -105,9 +105,9 @@ pub use crate::config_constraint::ConstraintResult;
pub(crate) use defaults::merge_with_default_agents;
pub(crate) use validation::upgrade_legacy_model_slugs;
pub(crate) const OPENAI_DEFAULT_MODEL: &str = "gpt-5.2-codex";
const OPENAI_DEFAULT_REVIEW_MODEL: &str = "gpt-5.2-codex";
pub const GPT_5_CODEX_MEDIUM_MODEL: &str = "gpt-5.2-codex";
pub(crate) const OPENAI_DEFAULT_MODEL: &str = "gpt-5.3-codex";
const OPENAI_DEFAULT_REVIEW_MODEL: &str = "gpt-5.3-codex";
pub const GPT_5_CODEX_MEDIUM_MODEL: &str = "gpt-5.3-codex";
/// Maximum number of bytes of the documentation that will be embedded. Larger
/// files are *silently truncated* to this size so we do not take up too much of
@@ -1,6 +1,7 @@
use serde::Deserialize;
use serde::Serialize;
use code_protocol::models::PermissionProfile;
use code_protocol::models::SandboxPermissions;
use crate::exec_command::session_id::SessionId;
@@ -27,6 +28,9 @@ pub struct ExecCommandParams {
#[serde(default)]
pub(crate) sandbox_permissions: Option<SandboxPermissions>,
#[serde(default)]
pub(crate) additional_permissions: Option<PermissionProfile>,
#[serde(default)]
pub(crate) justification: Option<String>,
}
+48 -2
View File
@@ -61,10 +61,14 @@ pub fn create_exec_command_tool_for_responses_api() -> ResponsesApiTool {
"sandbox_permissions".to_string(),
JsonSchema::String {
description: Some(
"Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
"Sandbox permissions for the command. Use \"with_additional_permissions\" to request additional sandboxed filesystem access (preferred), or \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
.to_string(),
),
allowed_values: None,
allowed_values: Some(vec![
"use_default".to_string(),
"with_additional_permissions".to_string(),
"require_escalated".to_string(),
]),
},
);
properties.insert(
@@ -77,6 +81,48 @@ pub fn create_exec_command_tool_for_responses_api() -> ResponsesApiTool {
allowed_values: None,
},
);
properties.insert(
"additional_permissions".to_string(),
JsonSchema::Object {
properties: BTreeMap::from([(
"file_system".to_string(),
JsonSchema::Object {
properties: BTreeMap::from([
(
"read".to_string(),
JsonSchema::Array {
items: Box::new(JsonSchema::String {
description: None,
allowed_values: None,
}),
description: Some(
"Additional filesystem paths to grant read access for this command."
.to_string(),
),
},
),
(
"write".to_string(),
JsonSchema::Array {
items: Box::new(JsonSchema::String {
description: None,
allowed_values: None,
}),
description: Some(
"Additional filesystem paths to grant write access for this command."
.to_string(),
),
},
),
]),
required: None,
additional_properties: Some(false.into()),
},
)]),
required: Some(vec!["file_system".to_string()]),
additional_properties: Some(false.into()),
},
);
ResponsesApiTool {
name: EXEC_COMMAND_TOOL_NAME.to_owned(),
@@ -433,9 +433,16 @@ async fn create_exec_command_session(
shell,
login,
sandbox_permissions: _,
additional_permissions,
justification: _,
} = params;
if additional_permissions.is_some() {
tracing::debug!(
"exec_command additional_permissions is accepted for compatibility but not yet enforced"
);
}
// Use the native pty implementation for the system
let pty_system = native_pty_system();
@@ -584,6 +591,7 @@ PY"#
shell: "/bin/bash".to_string(),
login: false,
sandbox_permissions: None,
additional_permissions: None,
justification: None,
};
let initial_output = match session_manager
+51 -2
View File
@@ -568,10 +568,14 @@ fn create_shell_tool_for_sandbox(sandbox_policy: &SandboxPolicy) -> OpenAiTool {
"sandbox_permissions".to_string(),
JsonSchema::String {
description: Some(
"Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
"Sandbox permissions for the command. Use \"with_additional_permissions\" to request additional sandboxed filesystem access (preferred), or \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
.to_string(),
),
allowed_values: Some(vec!["use_default".to_string(), "require_escalated".to_string()]),
allowed_values: Some(vec![
"use_default".to_string(),
"with_additional_permissions".to_string(),
"require_escalated".to_string(),
]),
},
);
properties.insert(
@@ -584,6 +588,48 @@ fn create_shell_tool_for_sandbox(sandbox_policy: &SandboxPolicy) -> OpenAiTool {
allowed_values: None,
},
);
properties.insert(
"additional_permissions".to_string(),
JsonSchema::Object {
properties: BTreeMap::from([(
"file_system".to_string(),
JsonSchema::Object {
properties: BTreeMap::from([
(
"read".to_string(),
JsonSchema::Array {
items: Box::new(JsonSchema::String {
description: None,
allowed_values: None,
}),
description: Some(
"Additional filesystem paths to grant read access for this command."
.to_string(),
),
},
),
(
"write".to_string(),
JsonSchema::Array {
items: Box::new(JsonSchema::String {
description: None,
allowed_values: None,
}),
description: Some(
"Additional filesystem paths to grant write access for this command."
.to_string(),
),
},
),
]),
required: None,
additional_properties: Some(false.into()),
},
)]),
required: Some(vec!["file_system".to_string()]),
additional_properties: Some(false.into()),
},
);
}
let description = match sandbox_policy {
@@ -616,6 +662,9 @@ The shell tool is used to execute shell commands.
- When invoking a command that will require escalated privileges:
- Provide the sandbox_permissions parameter with the value \"require_escalated\"
- Include a short, 1 sentence explanation for why we need escalated permissions in the justification parameter.
- When additional sandboxed filesystem access is enough:
- Provide the sandbox_permissions parameter with the value \"with_additional_permissions\"
- Provide additional_permissions.file_system.read and/or additional_permissions.file_system.write with the minimal paths needed.
Long-running commands may be backgrounded after an initial window. Use `wait` to await background tasks. Optional `timeout` can set a hard kill if needed."#,
roots_str,
+105 -4
View File
@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use code_utils_image::load_and_resize_to_fit;
use serde::Deserialize;
@@ -35,12 +36,84 @@ pub enum SandboxPermissions {
UseDefault,
/// Request to run outside the sandbox
RequireEscalated,
/// Request additional sandbox permissions for this command.
WithAdditionalPermissions,
}
impl SandboxPermissions {
pub fn requires_escalated_permissions(self) -> bool {
matches!(self, SandboxPermissions::RequireEscalated)
}
pub fn requires_additional_permissions(self) -> bool {
!matches!(self, SandboxPermissions::UseDefault)
}
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
pub struct FileSystemPermissions {
pub read: Option<Vec<PathBuf>>,
pub write: Option<Vec<PathBuf>>,
}
impl FileSystemPermissions {
pub fn is_empty(&self) -> bool {
self.read.is_none() && self.write.is_none()
}
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
pub struct MacOsPermissions {
pub preferences: Option<MacOsPreferencesValue>,
pub automations: Option<MacOsAutomationValue>,
pub accessibility: Option<bool>,
pub calendar: Option<bool>,
}
impl MacOsPermissions {
pub fn is_empty(&self) -> bool {
self.preferences.is_none()
&& self.automations.is_none()
&& self.accessibility.is_none()
&& self.calendar.is_none()
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum MacOsPreferencesValue {
Bool(bool),
Mode(String),
}
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum MacOsAutomationValue {
Bool(bool),
BundleIds(Vec<String>),
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
pub struct PermissionProfile {
pub network: Option<bool>,
pub file_system: Option<FileSystemPermissions>,
pub macos: Option<MacOsPermissions>,
}
impl PermissionProfile {
pub fn is_empty(&self) -> bool {
self.network.is_none()
&& self
.file_system
.as_ref()
.map(FileSystemPermissions::is_empty)
.unwrap_or(true)
&& self
.macos
.as_ref()
.map(MacOsPermissions::is_empty)
.unwrap_or(true)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
@@ -227,6 +300,8 @@ const APPROVAL_POLICY_ON_FAILURE: &str =
include_str!("prompts/permissions/approval_policy/on_failure.md");
const APPROVAL_POLICY_ON_REQUEST_RULE: &str =
include_str!("prompts/permissions/approval_policy/on_request_rule.md");
const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION: &str =
include_str!("prompts/permissions/approval_policy/on_request_rule_request_permission.md");
const SANDBOX_MODE_DANGER_FULL_ACCESS: &str =
include_str!("prompts/permissions/sandbox_mode/danger_full_access.md");
@@ -239,16 +314,25 @@ impl DeveloperInstructions {
Self { text: text.into() }
}
pub fn from(approval_policy: AskForApproval, exec_policy: &Policy) -> DeveloperInstructions {
pub fn from(
approval_policy: AskForApproval,
exec_policy: &Policy,
request_permission_enabled: bool,
) -> DeveloperInstructions {
let on_request_instructions = || {
let on_request_rule = if request_permission_enabled {
APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION
} else {
APPROVAL_POLICY_ON_REQUEST_RULE
};
let command_prefixes = format_allow_prefixes(exec_policy.get_allowed_prefixes());
match command_prefixes {
Some(prefixes) => {
format!(
"{APPROVAL_POLICY_ON_REQUEST_RULE}\n## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"
"{on_request_rule}\n## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"
)
}
None => APPROVAL_POLICY_ON_REQUEST_RULE.to_string(),
None => on_request_rule.to_string(),
}
};
let text = match approval_policy {
@@ -306,6 +390,7 @@ impl DeveloperInstructions {
approval_policy: AskForApproval,
exec_policy: &Policy,
cwd: &Path,
request_permission_enabled: bool,
) -> Self {
let network_access = if sandbox_policy.has_full_network_access() {
NetworkAccess::Enabled
@@ -329,6 +414,7 @@ impl DeveloperInstructions {
approval_policy,
exec_policy,
writable_roots,
request_permission_enabled,
)
}
@@ -352,6 +438,7 @@ impl DeveloperInstructions {
approval_policy: AskForApproval,
exec_policy: &Policy,
writable_roots: Option<Vec<WritableRoot>>,
request_permission_enabled: bool,
) -> Self {
let start_tag = DeveloperInstructions::new("<permissions instructions>");
let end_tag = DeveloperInstructions::new("</permissions instructions>");
@@ -360,7 +447,11 @@ impl DeveloperInstructions {
sandbox_mode,
network_access,
))
.concat(DeveloperInstructions::from(approval_policy, exec_policy))
.concat(DeveloperInstructions::from(
approval_policy,
exec_policy,
request_permission_enabled,
))
.concat(DeveloperInstructions::from_writable_roots(writable_roots))
.concat(end_tag)
}
@@ -754,6 +845,9 @@ pub struct ShellToolCallParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub prefix_rule: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub additional_permissions: Option<PermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub justification: Option<String>,
}
@@ -777,6 +871,9 @@ pub struct ShellCommandToolCallParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub prefix_rule: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub additional_permissions: Option<PermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub justification: Option<String>,
}
@@ -1220,6 +1317,7 @@ mod tests {
AskForApproval::OnRequest,
&empty_exec_policy(),
None,
false,
);
let text = instructions.into_text();
@@ -1248,6 +1346,7 @@ mod tests {
AskForApproval::UnlessTrusted,
&empty_exec_policy(),
&PathBuf::from("/tmp"),
false,
);
let text = instructions.into_text();
assert!(text.contains("Network access is enabled."));
@@ -1263,6 +1362,7 @@ mod tests {
AskForApproval::OnRequest,
&exec_policy,
None,
false,
);
let text = instructions.into_text();
@@ -1574,6 +1674,7 @@ mod tests {
timeout_ms: Some(1000),
sandbox_permissions: None,
prefix_rule: None,
additional_permissions: None,
justification: None,
},
params
+9 -24
View File
@@ -4,7 +4,6 @@
//! are used to preserve compatibility when older payloads omit newly introduced attributes.
use std::collections::HashMap;
use std::collections::HashSet;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -420,32 +419,18 @@ impl ModelPreset {
.collect()
}
/// Merge remote presets with existing presets, preferring remote when slugs match.
/// Recompute the single default preset using picker visibility.
///
/// Remote presets take precedence. Existing presets not in remote are appended with `is_default` set to false.
pub fn merge(
remote_presets: Vec<ModelPreset>,
existing_presets: Vec<ModelPreset>,
) -> Vec<ModelPreset> {
if remote_presets.is_empty() {
return existing_presets;
}
let remote_slugs: HashSet<&str> = remote_presets
.iter()
.map(|preset| preset.model.as_str())
.collect();
let mut merged_presets = remote_presets.clone();
for mut preset in existing_presets {
if remote_slugs.contains(preset.model.as_str()) {
continue;
}
/// The first picker-visible model wins; if none are picker-visible, the first model wins.
pub fn mark_default_by_picker_visibility(models: &mut [ModelPreset]) {
for preset in models.iter_mut() {
preset.is_default = false;
merged_presets.push(preset);
}
merged_presets
if let Some(default) = models.iter_mut().find(|preset| preset.show_in_picker) {
default.is_default = true;
} else if let Some(default) = models.first_mut() {
default.is_default = true;
}
}
}
@@ -0,0 +1,30 @@
# Permission Requests
Commands may require user approval before execution. Prefer requesting sandboxed additional permissions instead of asking to run fully outside the sandbox.
## Preferred request mode
When you need extra filesystem access for one command, use:
- `sandbox_permissions: "with_additional_permissions"`
- `additional_permissions` with one or both fields:
- `file_system.read`: list of paths that need read access
- `file_system.write`: list of paths that need write access
This keeps execution inside the current sandbox policy, while adding only the requested permissions for that command, unless an exec-policy allow rule applies and authorizes running the command outside the sandbox.
If the command already matches an exec-policy allow rule, the command can be auto-approved without an extra prompt. In that case, exec-policy allow behavior (including any sandbox bypass) takes precedence.
## Escalation Requests
Use full escalation only when sandboxed additional permissions cannot satisfy the task.
- `sandbox_permissions: "require_escalated"`
- Include `justification` as a short question asking for approval.
- Optionally include `prefix_rule` to suggest a reusable allow rule.
## Command segmentation reminder
The command string is split into independent command segments at shell control operators, including pipes (`|`), logical operators (`&&`, `||`), command separators (`;`), and subshell boundaries (`(...)`, `$()`).
Each segment is evaluated independently for sandbox restrictions and approval requirements.