Skip to content

Commit 30f0162

Browse files
committed
Constrain values for approval_policy
1 parent 80140c6 commit 30f0162

File tree

16 files changed

+629
-104
lines changed

16 files changed

+629
-104
lines changed

codex-rs/common/src/config_summary.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub fn create_config_summary_entries(config: &Config) -> Vec<(&'static str, Stri
99
("workdir", config.cwd.display().to_string()),
1010
("model", config.model.clone()),
1111
("provider", config.model_provider_id.clone()),
12-
("approval", config.approval_policy.to_string()),
12+
("approval", config.approval_policy.value().to_string()),
1313
("sandbox", summarize_sandbox_policy(&config.sandbox_policy)),
1414
];
1515
if config.model_provider.wire_api == WireApi::Responses {

codex-rs/core/src/codex.rs

Lines changed: 109 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ use crate::client_common::Prompt;
7070
use crate::client_common::ResponseEvent;
7171
use crate::compact::collect_user_messages;
7272
use crate::config::Config;
73+
use crate::config::Constrained;
74+
use crate::config::ConstraintError;
75+
use crate::config::ConstraintResult;
7376
use crate::config::types::ShellEnvironmentPolicy;
7477
use crate::context_manager::ContextManager;
7578
use crate::environment_context::EnvironmentContext;
@@ -87,6 +90,7 @@ use crate::protocol::ApplyPatchApprovalRequestEvent;
8790
use crate::protocol::AskForApproval;
8891
use crate::protocol::BackgroundEventEvent;
8992
use crate::protocol::DeprecationNoticeEvent;
93+
use crate::protocol::ErrorEvent;
9094
use crate::protocol::Event;
9195
use crate::protocol::EventMsg;
9296
use crate::protocol::ExecApprovalRequestEvent;
@@ -191,7 +195,7 @@ impl Codex {
191195
user_instructions,
192196
base_instructions: config.base_instructions.clone(),
193197
compact_prompt: config.compact_prompt.clone(),
194-
approval_policy: config.approval_policy,
198+
approval_policy: config.approval_policy.clone(),
195199
sandbox_policy: config.sandbox_policy.clone(),
196200
cwd: config.cwd.clone(),
197201
original_config_do_not_use: Arc::clone(&config),
@@ -339,7 +343,7 @@ pub(crate) struct SessionConfiguration {
339343
compact_prompt: Option<String>,
340344

341345
/// When to escalate for approval for execution
342-
approval_policy: AskForApproval,
346+
approval_policy: Constrained<AskForApproval>,
343347
/// How to sandbox commands executed in the system
344348
sandbox_policy: SandboxPolicy,
345349

@@ -362,7 +366,7 @@ pub(crate) struct SessionConfiguration {
362366
}
363367

364368
impl SessionConfiguration {
365-
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> Self {
369+
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult<Self> {
366370
let mut next_configuration = self.clone();
367371
if let Some(model) = updates.model.clone() {
368372
next_configuration.model = model;
@@ -374,15 +378,15 @@ impl SessionConfiguration {
374378
next_configuration.model_reasoning_summary = summary;
375379
}
376380
if let Some(approval_policy) = updates.approval_policy {
377-
next_configuration.approval_policy = approval_policy;
381+
next_configuration.approval_policy.set(approval_policy)?;
378382
}
379383
if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
380384
next_configuration.sandbox_policy = sandbox_policy;
381385
}
382386
if let Some(cwd) = updates.cwd.clone() {
383387
next_configuration.cwd = cwd;
384388
}
385-
next_configuration
389+
Ok(next_configuration)
386390
}
387391
}
388392

@@ -450,7 +454,7 @@ impl Session {
450454
base_instructions: session_configuration.base_instructions.clone(),
451455
compact_prompt: session_configuration.compact_prompt.clone(),
452456
user_instructions: session_configuration.user_instructions.clone(),
453-
approval_policy: session_configuration.approval_policy,
457+
approval_policy: session_configuration.approval_policy.value(),
454458
sandbox_policy: session_configuration.sandbox_policy.clone(),
455459
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
456460
tools_config,
@@ -566,7 +570,7 @@ impl Session {
566570
config.model_reasoning_summary,
567571
config.model_context_window,
568572
config.model_auto_compact_token_limit,
569-
config.approval_policy,
573+
config.approval_policy.value(),
570574
config.sandbox_policy.clone(),
571575
config.mcp_servers.keys().map(String::as_str).collect(),
572576
config.active_profile.clone(),
@@ -609,7 +613,7 @@ impl Session {
609613
session_id: conversation_id,
610614
model: session_configuration.model.clone(),
611615
model_provider_id: config.model_provider_id.clone(),
612-
approval_policy: session_configuration.approval_policy,
616+
approval_policy: session_configuration.approval_policy.value(),
613617
sandbox_policy: session_configuration.sandbox_policy.clone(),
614618
cwd: session_configuration.cwd.clone(),
615619
reasoning_effort: session_configuration.model_reasoning_effort,
@@ -688,7 +692,7 @@ impl Session {
688692
}
689693

690694
async fn record_initial_history(&self, conversation_history: InitialHistory) {
691-
let turn_context = self.new_turn(SessionSettingsUpdate::default()).await;
695+
let turn_context = self.new_default_turn().await;
692696
match conversation_history {
693697
InitialHistory::New => {
694698
// Build and record initial items (user instructions + environment context)
@@ -747,29 +751,71 @@ impl Session {
747751
}
748752
}
749753

750-
pub(crate) async fn update_settings(&self, updates: SessionSettingsUpdate) {
754+
pub(crate) async fn update_settings(
755+
&self,
756+
updates: SessionSettingsUpdate,
757+
) -> ConstraintResult<()> {
751758
let mut state = self.state.lock().await;
752759

753-
state.session_configuration = state.session_configuration.apply(&updates);
754-
}
755-
756-
pub(crate) async fn new_turn(&self, updates: SessionSettingsUpdate) -> Arc<TurnContext> {
757-
let sub_id = self.next_internal_sub_id();
758-
self.new_turn_with_sub_id(sub_id, updates).await
760+
match state.session_configuration.apply(&updates) {
761+
Ok(updated) => {
762+
state.session_configuration = updated;
763+
Ok(())
764+
}
765+
Err(err) => {
766+
let wrapped = ConstraintError {
767+
message: format!("Could not update config: {err}"),
768+
};
769+
warn!(%wrapped, "rejected session settings update");
770+
Err(wrapped)
771+
}
772+
}
759773
}
760774

761775
pub(crate) async fn new_turn_with_sub_id(
762776
&self,
763777
sub_id: String,
764778
updates: SessionSettingsUpdate,
765-
) -> Arc<TurnContext> {
779+
) -> ConstraintResult<Arc<TurnContext>> {
766780
let session_configuration = {
767781
let mut state = self.state.lock().await;
768-
let session_configuration = state.session_configuration.clone().apply(&updates);
769-
state.session_configuration = session_configuration.clone();
770-
session_configuration
782+
match state.session_configuration.clone().apply(&updates) {
783+
Ok(next) => {
784+
state.session_configuration = next.clone();
785+
next
786+
}
787+
Err(err) => {
788+
let wrapped = ConstraintError {
789+
message: format!("Could not update config: {err}"),
790+
};
791+
self.send_event_raw(Event {
792+
id: sub_id.clone(),
793+
msg: EventMsg::Error(ErrorEvent {
794+
message: wrapped.to_string(),
795+
codex_error_info: Some(CodexErrorInfo::BadRequest),
796+
}),
797+
})
798+
.await;
799+
return Err(wrapped);
800+
}
801+
}
771802
};
772803

804+
Ok(self
805+
.new_turn_from_configuration(
806+
sub_id,
807+
session_configuration,
808+
updates.final_output_json_schema,
809+
)
810+
.await)
811+
}
812+
813+
async fn new_turn_from_configuration(
814+
&self,
815+
sub_id: String,
816+
session_configuration: SessionConfiguration,
817+
final_output_json_schema: Option<Option<Value>>,
818+
) -> Arc<TurnContext> {
773819
let per_turn_config = Self::build_per_turn_config(&session_configuration);
774820
let model_family = self
775821
.services
@@ -786,12 +832,26 @@ impl Session {
786832
self.conversation_id,
787833
sub_id,
788834
);
789-
if let Some(final_schema) = updates.final_output_json_schema {
835+
if let Some(final_schema) = final_output_json_schema {
790836
turn_context.final_output_json_schema = final_schema;
791837
}
792838
Arc::new(turn_context)
793839
}
794840

841+
pub(crate) async fn new_default_turn(&self) -> Arc<TurnContext> {
842+
self.new_default_turn_with_sub_id(self.next_internal_sub_id())
843+
.await
844+
}
845+
846+
pub(crate) async fn new_default_turn_with_sub_id(&self, sub_id: String) -> Arc<TurnContext> {
847+
let session_configuration = {
848+
let state = self.state.lock().await;
849+
state.session_configuration.clone()
850+
};
851+
self.new_turn_from_configuration(sub_id, session_configuration, None)
852+
.await
853+
}
854+
795855
fn build_environment_update_item(
796856
&self,
797857
previous: Option<&Arc<TurnContext>>,
@@ -1462,8 +1522,7 @@ impl Session {
14621522

14631523
async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiver<Submission>) {
14641524
// Seed with context in case there is an OverrideTurnContext first.
1465-
let mut previous_context: Option<Arc<TurnContext>> =
1466-
Some(sess.new_turn(SessionSettingsUpdate::default()).await);
1525+
let mut previous_context: Option<Arc<TurnContext>> = Some(sess.new_default_turn().await);
14671526

14681527
if config.features.enabled(Feature::RemoteModels)
14691528
&& let Err(err) = sess
@@ -1492,6 +1551,7 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
14921551
} => {
14931552
handlers::override_turn_context(
14941553
&sess,
1554+
sub.id.clone(),
14951555
SessionSettingsUpdate {
14961556
cwd,
14971557
approval_policy,
@@ -1602,8 +1662,21 @@ mod handlers {
16021662
sess.interrupt_task().await;
16031663
}
16041664

1605-
pub async fn override_turn_context(sess: &Session, updates: SessionSettingsUpdate) {
1606-
sess.update_settings(updates).await;
1665+
pub async fn override_turn_context(
1666+
sess: &Session,
1667+
sub_id: String,
1668+
updates: SessionSettingsUpdate,
1669+
) {
1670+
if let Err(err) = sess.update_settings(updates).await {
1671+
sess.send_event_raw(Event {
1672+
id: sub_id,
1673+
msg: EventMsg::Error(ErrorEvent {
1674+
message: err.to_string(),
1675+
codex_error_info: Some(CodexErrorInfo::BadRequest),
1676+
}),
1677+
})
1678+
.await;
1679+
}
16071680
}
16081681

16091682
pub async fn user_input_or_turn(
@@ -1638,7 +1711,9 @@ mod handlers {
16381711
_ => unreachable!(),
16391712
};
16401713

1641-
let current_context = sess.new_turn_with_sub_id(sub_id, updates).await;
1714+
let Ok(current_context) = sess.new_turn_with_sub_id(sub_id, updates).await else {
1715+
return;
1716+
};
16421717
current_context
16431718
.client
16441719
.get_otel_event_manager()
@@ -1665,9 +1740,7 @@ mod handlers {
16651740
command: String,
16661741
previous_context: &mut Option<Arc<TurnContext>>,
16671742
) {
1668-
let turn_context = sess
1669-
.new_turn_with_sub_id(sub_id, SessionSettingsUpdate::default())
1670-
.await;
1743+
let turn_context = sess.new_default_turn_with_sub_id(sub_id).await;
16711744
sess.spawn_task(
16721745
Arc::clone(&turn_context),
16731746
Vec::new(),
@@ -1822,17 +1895,13 @@ mod handlers {
18221895
}
18231896

18241897
pub async fn undo(sess: &Arc<Session>, sub_id: String) {
1825-
let turn_context = sess
1826-
.new_turn_with_sub_id(sub_id, SessionSettingsUpdate::default())
1827-
.await;
1898+
let turn_context = sess.new_default_turn_with_sub_id(sub_id).await;
18281899
sess.spawn_task(turn_context, Vec::new(), UndoTask::new())
18291900
.await;
18301901
}
18311902

18321903
pub async fn compact(sess: &Arc<Session>, sub_id: String) {
1833-
let turn_context = sess
1834-
.new_turn_with_sub_id(sub_id, SessionSettingsUpdate::default())
1835-
.await;
1904+
let turn_context = sess.new_default_turn_with_sub_id(sub_id).await;
18361905

18371906
sess.spawn_task(
18381907
Arc::clone(&turn_context),
@@ -1886,9 +1955,7 @@ mod handlers {
18861955
sub_id: String,
18871956
review_request: ReviewRequest,
18881957
) {
1889-
let turn_context = sess
1890-
.new_turn_with_sub_id(sub_id.clone(), SessionSettingsUpdate::default())
1891-
.await;
1958+
let turn_context = sess.new_default_turn_with_sub_id(sub_id.clone()).await;
18921959
match resolve_review_request(review_request, config.cwd.as_path()) {
18931960
Ok(resolved) => {
18941961
spawn_review_thread(
@@ -2595,7 +2662,7 @@ mod tests {
25952662
user_instructions: config.user_instructions.clone(),
25962663
base_instructions: config.base_instructions.clone(),
25972664
compact_prompt: config.compact_prompt.clone(),
2598-
approval_policy: config.approval_policy,
2665+
approval_policy: config.approval_policy.clone(),
25992666
sandbox_policy: config.sandbox_policy.clone(),
26002667
cwd: config.cwd.clone(),
26012668
original_config_do_not_use: Arc::clone(&config),
@@ -2666,7 +2733,7 @@ mod tests {
26662733
user_instructions: config.user_instructions.clone(),
26672734
base_instructions: config.base_instructions.clone(),
26682735
compact_prompt: config.compact_prompt.clone(),
2669-
approval_policy: config.approval_policy,
2736+
approval_policy: config.approval_policy.clone(),
26702737
sandbox_policy: config.sandbox_policy.clone(),
26712738
cwd: config.cwd.clone(),
26722739
original_config_do_not_use: Arc::clone(&config),
@@ -2867,7 +2934,7 @@ mod tests {
28672934
user_instructions: config.user_instructions.clone(),
28682935
base_instructions: config.base_instructions.clone(),
28692936
compact_prompt: config.compact_prompt.clone(),
2870-
approval_policy: config.approval_policy,
2937+
approval_policy: config.approval_policy.clone(),
28712938
sandbox_policy: config.sandbox_policy.clone(),
28722939
cwd: config.cwd.clone(),
28732940
original_config_do_not_use: Arc::clone(&config),
@@ -2949,7 +3016,7 @@ mod tests {
29493016
user_instructions: config.user_instructions.clone(),
29503017
base_instructions: config.base_instructions.clone(),
29513018
compact_prompt: config.compact_prompt.clone(),
2952-
approval_policy: config.approval_policy,
3019+
approval_policy: config.approval_policy.clone(),
29533020
sandbox_policy: config.sandbox_policy.clone(),
29543021
cwd: config.cwd.clone(),
29553022
original_config_do_not_use: Arc::clone(&config),

0 commit comments

Comments
 (0)