Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ Use `thread/backgroundTerminals/clean` to terminate all running background termi
### Example: Steer an active turn

Use `turn/steer` to append additional user input to the currently active regular turn. This does
not emit `turn/started` and does not accept turn context overrides.
not emit `turn/started` and does not accept thread settings overrides.

```json
{ "method": "turn/steer", "id": 32, "params": {
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/app-server/src/request_processors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ use codex_config::ConfigLayerStack;
use codex_config::loader::project_trust_key;
use codex_config::types::McpServerTransportConfig;
use codex_core::CodexThread;
use codex_core::CodexThreadTurnContextOverrides;
use codex_core::CodexThreadSettingsOverrides;
use codex_core::ExternalGoalPreviousStatus;
use codex_core::ExternalGoalSet;
use codex_core::ForkSnapshot;
Expand Down
9 changes: 6 additions & 3 deletions codex-rs/app-server/src/request_processors/turn_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ impl TurnRequestProcessor {
warning.contains("Configured value for `permission_profile` is disallowed")
}) {
return Err(invalid_request(format!(
"invalid turn context override: {warning}"
"invalid thread settings override: {warning}"
)));
}
(
Expand All @@ -482,7 +482,7 @@ impl TurnRequestProcessor {
// still queued together with the input below to preserve submission order.
if has_any_overrides {
thread
.validate_turn_context_overrides(CodexThreadTurnContextOverrides {
.validate_thread_settings_overrides(CodexThreadSettingsOverrides {
cwd: cwd.clone(),
workspace_roots: runtime_workspace_roots.clone(),
approval_policy,
Expand All @@ -500,7 +500,9 @@ impl TurnRequestProcessor {
personality,
})
.await
.map_err(|err| invalid_request(format!("invalid turn context override: {err}")))?;
.map_err(|err| {
invalid_request(format!("invalid thread settings override: {err}"))
})?;
}

// Start the turn by submitting the user input. Return its submission id as turn_id.
Expand Down Expand Up @@ -532,6 +534,7 @@ impl TurnRequestProcessor {
environments: environment_selections,
final_output_json_schema: params.output_schema,
responsesapi_client_metadata: params.responsesapi_client_metadata,
thread_settings: Default::default(),
}
};
let turn_id = self
Expand Down
6 changes: 5 additions & 1 deletion codex-rs/app-server/tests/suite/v2/turn_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,11 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn()
.await??;

assert_eq!(err.error.code, INVALID_REQUEST_ERROR_CODE);
assert!(err.error.message.contains("invalid turn context override"));
assert!(
err.error
.message
.contains("invalid thread settings override")
);
assert!(
err.error.message.contains("allowed set [ReadOnly]"),
"unexpected error message: {}",
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/core/src/agent/control_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ async fn send_input_submits_user_message() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
},
);
let captured = harness
Expand Down Expand Up @@ -589,6 +590,7 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
},
);
let captured = harness
Expand Down Expand Up @@ -737,6 +739,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
},
);
let captured = harness
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/codex_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ pub(crate) async fn run_codex_thread_one_shot(
items: input,
final_output_json_schema,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await?;

Expand Down
12 changes: 6 additions & 6 deletions codex-rs/core/src/codex_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ impl ThreadConfigSnapshot {
}
}

/// Turn context overrides that app-server validates before starting a turn.
/// Thread settings overrides that app-server validates before starting a turn.
#[derive(Clone, Default)]
pub struct CodexThreadTurnContextOverrides {
pub struct CodexThreadSettingsOverrides {
pub cwd: Option<PathBuf>,
pub workspace_roots: Option<Vec<AbsolutePathBuf>>,
pub profile_workspace_roots: Option<Vec<AbsolutePathBuf>>,
Expand Down Expand Up @@ -255,12 +255,12 @@ impl CodexThread {
.await
}

/// Validate persistent turn context overrides without committing them.
pub async fn validate_turn_context_overrides(
/// Validate persistent thread settings overrides without committing them.
pub async fn validate_thread_settings_overrides(
&self,
overrides: CodexThreadTurnContextOverrides,
overrides: CodexThreadSettingsOverrides,
) -> ConstraintResult<()> {
let CodexThreadTurnContextOverrides {
let CodexThreadSettingsOverrides {
cwd,
workspace_roots,
profile_workspace_roots,
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod compact_remote;
mod compact_remote_v2;
mod config_lock;
pub use codex_thread::CodexThread;
pub use codex_thread::CodexThreadTurnContextOverrides;
pub use codex_thread::CodexThreadSettingsOverrides;
pub use codex_thread::ThreadConfigSnapshot;
pub use session::turn_context::TurnContext;
mod agent;
Expand Down
103 changes: 73 additions & 30 deletions codex-rs/core/src/session/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::ThreadMemoryMode;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::ThreadSettingsOverrides;
use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::WarningEvent;
use codex_protocol::request_permissions::RequestPermissionsResponse;
Expand Down Expand Up @@ -184,20 +185,9 @@ pub(super) async fn user_input_or_turn_inner(
personality,
environments,
} => {
let collaboration_mode = if let Some(collab_mode) = collaboration_mode {
Some(collab_mode)
} else {
let state = sess.state.lock().await;
Some(
state
.session_configuration
.collaboration_mode
.with_updates(model, effort, /*developer_instructions*/ None),
)
};
(
items,
SessionSettingsUpdate {
let mut updates = thread_settings_update(
sess,
ThreadSettingsOverrides {
cwd,
workspace_roots,
profile_workspace_roots,
Expand All @@ -207,32 +197,35 @@ pub(super) async fn user_input_or_turn_inner(
permission_profile,
active_permission_profile,
windows_sandbox_level,
collaboration_mode,
reasoning_summary: summary,
model,
effort,
summary,
service_tier,
final_output_json_schema: Some(final_output_json_schema),
environments,
collaboration_mode,
personality,
app_server_client_name: None,
app_server_client_version: None,
},
responsesapi_client_metadata,
)
.await;
updates.final_output_json_schema = Some(final_output_json_schema);
updates.environments = environments;
(items, updates, responsesapi_client_metadata)
}
Op::UserInput {
items,
environments,
final_output_json_schema,
responsesapi_client_metadata,
} => (
items,
SessionSettingsUpdate {
final_output_json_schema: Some(final_output_json_schema),
environments,
..Default::default()
},
responsesapi_client_metadata,
),
thread_settings,
} => {
let mut updates = if thread_settings == ThreadSettingsOverrides::default() {
SessionSettingsUpdate::default()
} else {
thread_settings_update(sess, thread_settings).await
};
updates.final_output_json_schema = Some(final_output_json_schema);
updates.environments = environments;
(items, updates, responsesapi_client_metadata)
}
_ => unreachable!(),
};

Expand Down Expand Up @@ -289,6 +282,56 @@ pub(super) async fn user_input_or_turn_inner(
}
}

async fn thread_settings_update(
sess: &Session,
thread_settings: ThreadSettingsOverrides,
) -> SessionSettingsUpdate {
let ThreadSettingsOverrides {
cwd,
workspace_roots,
profile_workspace_roots,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
windows_sandbox_level,
model,
effort,
summary,
service_tier,
collaboration_mode,
personality,
} = thread_settings;
let collaboration_mode = if let Some(collaboration_mode) = collaboration_mode {
collaboration_mode
} else {
let state = sess.state.lock().await;
// Model and reasoning effort live in CollaborationMode settings today, so
// partial thread-settings updates refresh those fields on the active mode.
state
.session_configuration
.collaboration_mode
.with_updates(model, effort, /*developer_instructions*/ None)
Comment thread
etraut-openai marked this conversation as resolved.
};
SessionSettingsUpdate {
cwd,
workspace_roots,
profile_workspace_roots,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
windows_sandbox_level,
collaboration_mode: Some(collaboration_mode),
reasoning_summary: summary,
service_tier,
personality,
..Default::default()
}
}

async fn mirror_user_text_to_realtime(sess: &Arc<Session>, items: &[UserInput]) {
let text = UserMessageItem::new(items).message();
if text.is_empty() {
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,7 @@ impl Session {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
},
/*mirror_user_text_to_realtime*/ None,
)
Expand Down
6 changes: 6 additions & 0 deletions codex-rs/core/src/session/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2220,6 +2220,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await?;
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
Expand Down Expand Up @@ -2283,6 +2284,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await?;
wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
Expand Down Expand Up @@ -5228,6 +5230,7 @@ fn op_kind_distinguishes_turn_ops() {
items: vec![],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
}
.kind(),
"user_input"
Expand Down Expand Up @@ -8335,6 +8338,7 @@ async fn active_goal_continuation_runs_again_after_no_tool_turn() -> anyhow::Res
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await?;

Expand Down Expand Up @@ -8439,6 +8443,7 @@ async fn pending_request_user_input_does_not_spawn_extra_goal_continuation() ->
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await?;

Expand Down Expand Up @@ -8863,6 +8868,7 @@ async fn completed_goal_accounts_current_turn_tokens_before_tool_response() -> a
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await?;

Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/tools/handlers/multi_agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2470,6 +2470,7 @@ async fn send_input_accepts_structured_items() {
],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
};
let captured = manager
.captured_ops()
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/core/tests/suite/abort_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ async fn interrupt_long_running_tool_emits_turn_aborted() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await
.unwrap();
Expand Down Expand Up @@ -109,6 +110,7 @@ async fn interrupt_tool_records_history_entries() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await
.unwrap();
Expand All @@ -129,6 +131,7 @@ async fn interrupt_tool_records_history_entries() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await
.unwrap();
Expand Down Expand Up @@ -211,6 +214,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await
.unwrap();
Expand All @@ -231,6 +235,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
thread_settings: Default::default(),
})
.await
.unwrap();
Expand Down
Loading
Loading