refactor(app-server): share turn context override mapping

Extract a shared `TurnContextOverrides` helper in the app-server so
`turn/start` and `thread/turnContext/update` build
`Op::OverrideTurnContext` through the same conversion path.

This also simplifies the TUI-side request flow by passing a single
`ThreadTurnContextUpdateParams` payload instead of threading eleven
separate arguments through the session wrapper.
This commit is contained in:
Felipe Coury
2026-04-07 12:50:15 -03:00
parent 93154c3378
commit 6d7b2c860d
3 changed files with 108 additions and 96 deletions
@@ -348,6 +348,34 @@ struct ThreadListFilters {
search_term: Option<String>,
}
struct TurnContextOverrides {
cwd: Option<PathBuf>,
approval_policy: Option<AskForApproval>,
approvals_reviewer: Option<codex_app_server_protocol::ApprovalsReviewer>,
sandbox_policy: Option<codex_app_server_protocol::SandboxPolicy>,
model: Option<String>,
effort: Option<Option<codex_protocol::openai_models::ReasoningEffort>>,
summary: Option<codex_protocol::config_types::ReasoningSummary>,
service_tier: Option<Option<codex_protocol::config_types::ServiceTier>>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
}
impl TurnContextOverrides {
fn has_any_override(&self) -> bool {
self.cwd.is_some()
|| self.approval_policy.is_some()
|| self.approvals_reviewer.is_some()
|| self.sandbox_policy.is_some()
|| self.model.is_some()
|| self.effort.is_some()
|| self.summary.is_some()
|| self.service_tier.is_some()
|| self.collaboration_mode.is_some()
|| self.personality.is_some()
}
}
// Duration before a browser ChatGPT login attempt is abandoned.
const LOGIN_CHATGPT_TIMEOUT: Duration = Duration::from_secs(10 * 60);
const LOGIN_ISSUER_OVERRIDE_ENV_VAR: &str = "CODEX_APP_SERVER_LOGIN_ISSUER";
@@ -629,6 +657,37 @@ impl CodexMessageProcessor {
collaboration_mode
}
fn build_override_turn_context_op(
&self,
thread: &CodexThread,
overrides: TurnContextOverrides,
) -> Op {
let collaboration_modes_config = CollaborationModesConfig {
default_mode_request_user_input: thread.enabled(Feature::DefaultModeRequestUserInput),
};
let collaboration_mode = overrides.collaboration_mode.map(|mode| {
self.normalize_turn_start_collaboration_mode(mode, collaboration_modes_config)
});
Op::OverrideTurnContext {
cwd: overrides.cwd,
approval_policy: overrides.approval_policy.map(AskForApproval::to_core),
approvals_reviewer: overrides
.approvals_reviewer
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
sandbox_policy: overrides.sandbox_policy.map(|p| p.to_core()),
// The v2 app-server API does not expose Windows sandbox level yet;
// keep matching `turn/start` by leaving it unchanged here.
windows_sandbox_level: None,
model: overrides.model,
effort: overrides.effort,
summary: overrides.summary,
service_tier: overrides.service_tier,
collaboration_mode,
personality: overrides.personality,
}
}
fn review_request_from_target(
target: ApiReviewTarget,
) -> Result<(ReviewRequest, String), JSONRPCErrorError> {
@@ -2676,6 +2735,9 @@ impl CodexMessageProcessor {
.await;
}
/// Handle `thread/turnContext/update`: apply turn-context overrides (cwd, model,
/// approval policy, etc.) to a loaded thread by forwarding them as an
/// `Op::OverrideTurnContext` to the core session.
async fn thread_turn_context_update(
&self,
request_id: ConnectionRequestId,
@@ -2694,6 +2756,18 @@ impl CodexMessageProcessor {
collaboration_mode,
personality,
} = params;
let overrides = TurnContextOverrides {
cwd,
approval_policy,
approvals_reviewer,
sandbox_policy,
model,
effort,
summary,
service_tier,
collaboration_mode,
personality,
};
let (_, thread) = match self.load_thread(&thread_id).await {
Ok(v) => v,
Err(error) => {
@@ -2702,31 +2776,11 @@ impl CodexMessageProcessor {
}
};
let collaboration_modes_config = CollaborationModesConfig {
default_mode_request_user_input: thread.enabled(Feature::DefaultModeRequestUserInput),
};
let collaboration_mode = collaboration_mode.map(|mode| {
self.normalize_turn_start_collaboration_mode(mode, collaboration_modes_config)
});
match self
.submit_core_op(
&request_id,
thread.as_ref(),
Op::OverrideTurnContext {
cwd,
approval_policy: approval_policy.map(AskForApproval::to_core),
approvals_reviewer: approvals_reviewer
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
sandbox_policy: sandbox_policy.map(|p| p.to_core()),
windows_sandbox_level: None,
model,
effort,
summary,
service_tier,
collaboration_mode,
personality,
},
self.build_override_turn_context_op(thread.as_ref(), overrides),
)
.await
{
@@ -6554,52 +6608,32 @@ impl CodexMessageProcessor {
return;
}
let collaboration_modes_config = CollaborationModesConfig {
default_mode_request_user_input: thread.enabled(Feature::DefaultModeRequestUserInput),
};
let collaboration_mode = params.collaboration_mode.map(|mode| {
self.normalize_turn_start_collaboration_mode(mode, collaboration_modes_config)
});
// Map v2 input items to core input items.
let mapped_items: Vec<CoreInputItem> = params
.input
.into_iter()
.map(V2UserInput::into_core)
.collect();
let has_any_overrides = params.cwd.is_some()
|| params.approval_policy.is_some()
|| params.approvals_reviewer.is_some()
|| params.sandbox_policy.is_some()
|| params.model.is_some()
|| params.service_tier.is_some()
|| params.effort.is_some()
|| params.summary.is_some()
|| collaboration_mode.is_some()
|| params.personality.is_some();
let overrides = TurnContextOverrides {
cwd: params.cwd,
approval_policy: params.approval_policy,
approvals_reviewer: params.approvals_reviewer,
sandbox_policy: params.sandbox_policy,
model: params.model,
effort: params.effort.map(Some),
summary: params.summary,
service_tier: params.service_tier,
collaboration_mode: params.collaboration_mode,
personality: params.personality,
};
// If any overrides are provided, update the session turn context first.
if has_any_overrides {
if overrides.has_any_override() {
let _ = self
.submit_core_op(
&request_id,
thread.as_ref(),
Op::OverrideTurnContext {
cwd: params.cwd,
approval_policy: params.approval_policy.map(AskForApproval::to_core),
approvals_reviewer: params
.approvals_reviewer
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
sandbox_policy: params.sandbox_policy.map(|p| p.to_core()),
windows_sandbox_level: None,
model: params.model,
effort: params.effort.map(Some),
summary: params.summary,
service_tier: params.service_tier,
collaboration_mode,
personality: params.personality,
},
self.build_override_turn_context_op(thread.as_ref(), overrides),
)
.await;
}
+14 -13
View File
@@ -79,6 +79,7 @@ use codex_app_server_protocol::SkillsListResponse;
use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadLoadedListParams;
use codex_app_server_protocol::ThreadRollbackResponse;
use codex_app_server_protocol::ThreadTurnContextUpdateParams;
use codex_app_server_protocol::Turn;
use codex_app_server_protocol::TurnError as AppServerTurnError;
use codex_app_server_protocol::TurnStatus;
@@ -2417,19 +2418,19 @@ impl App {
personality,
} => {
app_server
.thread_turn_context_update(
thread_id,
(*cwd).clone(),
*approval_policy,
*approvals_reviewer,
(*sandbox_policy).clone(),
(*model).clone(),
*effort,
*summary,
*service_tier,
(*collaboration_mode).clone(),
*personality,
)
.thread_turn_context_update(ThreadTurnContextUpdateParams {
thread_id: thread_id.to_string(),
cwd: (*cwd).clone(),
approval_policy: (*approval_policy).map(Into::into),
approvals_reviewer: (*approvals_reviewer).map(Into::into),
sandbox_policy: (*sandbox_policy).clone().map(Into::into),
model: (*model).clone(),
effort: *effort,
summary: *summary,
service_tier: *service_tier,
collaboration_mode: (*collaboration_mode).clone(),
personality: *personality,
})
.await?;
Ok(true)
}
+5 -28
View File
@@ -646,40 +646,17 @@ impl AppServerSession {
Ok(())
}
#[allow(clippy::too_many_arguments)]
/// Send a `thread/turnContext/update` request to the app-server, applying
/// the given overrides (cwd, model, approval policy, etc.) to the loaded
/// thread so they take effect on subsequent turns and shell commands.
pub(crate) async fn thread_turn_context_update(
&mut self,
thread_id: ThreadId,
cwd: Option<PathBuf>,
approval_policy: Option<AskForApproval>,
approvals_reviewer: Option<codex_protocol::config_types::ApprovalsReviewer>,
sandbox_policy: Option<SandboxPolicy>,
model: Option<String>,
effort: Option<Option<codex_protocol::openai_models::ReasoningEffort>>,
summary: Option<codex_protocol::config_types::ReasoningSummary>,
service_tier: Option<Option<codex_protocol::config_types::ServiceTier>>,
collaboration_mode: Option<codex_protocol::config_types::CollaborationMode>,
personality: Option<codex_protocol::config_types::Personality>,
params: ThreadTurnContextUpdateParams,
) -> Result<()> {
let request_id = self.next_request_id();
let _: ThreadTurnContextUpdateResponse = self
.client
.request_typed(ClientRequest::ThreadTurnContextUpdate {
request_id,
params: ThreadTurnContextUpdateParams {
thread_id: thread_id.to_string(),
cwd,
approval_policy: approval_policy.map(Into::into),
approvals_reviewer: approvals_reviewer.map(Into::into),
sandbox_policy: sandbox_policy.map(Into::into),
model,
effort,
summary,
service_tier,
collaboration_mode,
personality,
},
})
.request_typed(ClientRequest::ThreadTurnContextUpdate { request_id, params })
.await
.wrap_err("thread/turnContext/update failed in TUI")?;
Ok(())