Compare commits

...

2 Commits

Author SHA1 Message Date
raju
50e02cbeb3 Support initial Codex time context overrides 2026-05-13 21:37:21 -07:00
raju
b4ecb42966 Support Codex turn context overrides 2026-05-13 20:05:15 -07:00
30 changed files with 451 additions and 3 deletions

View File

@@ -3763,6 +3763,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -3816,6 +3823,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -3938,6 +3952,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -4020,6 +4041,13 @@
}
],
"description": "Optional client-supplied analytics source classification for this thread."
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"type": "object"
@@ -4132,6 +4160,13 @@
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [
@@ -4208,6 +4243,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [

View File

@@ -16815,6 +16815,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -16868,6 +16875,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -17097,6 +17111,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -17179,6 +17200,13 @@
}
],
"description": "Optional client-supplied analytics source classification for this thread."
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"title": "ThreadStartParams",
@@ -17846,6 +17874,13 @@
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [
@@ -17922,6 +17957,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [

View File

@@ -14639,6 +14639,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -14692,6 +14699,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -14921,6 +14935,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -15003,6 +15024,13 @@
}
],
"description": "Optional client-supplied analytics source classification for this thread."
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"title": "ThreadStartParams",
@@ -15670,6 +15698,13 @@
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [
@@ -15746,6 +15781,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [

View File

@@ -1048,6 +1048,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -1101,6 +1108,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [

View File

@@ -231,6 +231,13 @@
"null"
]
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"type": [
"string",
@@ -313,6 +320,13 @@
}
],
"description": "Optional client-supplied analytics source classification for this thread."
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this thread and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"title": "ThreadStartParams",

View File

@@ -522,6 +522,13 @@
],
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
},
"currentDate": {
"description": "Override the current date shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"cwd": {
"description": "Override the working directory for this turn and subsequent turns.",
"type": [
@@ -598,6 +605,13 @@
},
"threadId": {
"type": "string"
},
"timezone": {
"description": "Override the timezone shown in the model-visible environment context for this turn and subsequent turns.",
"type": [
"string",
"null"
]
}
},
"required": [

View File

@@ -25,4 +25,12 @@ model?: string | null, modelProvider?: string | null, serviceTier?: string | nul
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null};
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, /**
* Override the current date shown in the model-visible environment
* context for this thread and subsequent turns.
*/
currentDate?: string | null, /**
* Override the timezone shown in the model-visible environment context
* for this thread and subsequent turns.
*/
timezone?: string | null, personality?: Personality | null};

View File

@@ -13,7 +13,15 @@ export type ThreadStartParams = {model?: string | null, modelProvider?: string |
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, /**
* Override the current date shown in the model-visible environment
* context for this thread and subsequent turns.
*/
currentDate?: string | null, /**
* Override the timezone shown in the model-visible environment context
* for this thread and subsequent turns.
*/
timezone?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
* Optional client-supplied analytics source classification for this thread.
*/
threadSource?: ThreadSource | null};

View File

@@ -14,6 +14,14 @@ export type TurnStartParams = {threadId: string, input: Array<UserInput>, /**
* Override the working directory for this turn and subsequent turns.
*/
cwd?: string | null, /**
* Override the current date shown in the model-visible environment context
* for this turn and subsequent turns.
*/
currentDate?: string | null, /**
* Override the timezone shown in the model-visible environment context for
* this turn and subsequent turns.
*/
timezone?: string | null, /**
* Override the approval policy for this turn and subsequent turns.
*/
approvalPolicy?: AskForApproval | null, /**

View File

@@ -3409,6 +3409,8 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
responsesapi_client_metadata: None,
environments: None,
cwd: None,
current_date: None,
timezone: None,
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,

View File

@@ -130,6 +130,14 @@ pub struct ThreadStartParams {
pub base_instructions: Option<String>,
#[ts(optional = nullable)]
pub developer_instructions: Option<String>,
/// Override the current date shown in the model-visible environment
/// context for this thread and subsequent turns.
#[ts(optional = nullable)]
pub current_date: Option<String>,
/// Override the timezone shown in the model-visible environment context
/// for this thread and subsequent turns.
#[ts(optional = nullable)]
pub timezone: Option<String>,
#[ts(optional = nullable)]
pub personality: Option<Personality>,
#[ts(optional = nullable)]
@@ -285,6 +293,14 @@ pub struct ThreadResumeParams {
pub base_instructions: Option<String>,
#[ts(optional = nullable)]
pub developer_instructions: Option<String>,
/// Override the current date shown in the model-visible environment
/// context for this thread and subsequent turns.
#[ts(optional = nullable)]
pub current_date: Option<String>,
/// Override the timezone shown in the model-visible environment context
/// for this thread and subsequent turns.
#[ts(optional = nullable)]
pub timezone: Option<String>,
#[ts(optional = nullable)]
pub personality: Option<Personality>,
/// When true, return only thread metadata and live-resume state without

View File

@@ -64,6 +64,14 @@ pub struct TurnStartParams {
/// Override the working directory for this turn and subsequent turns.
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
/// Override the current date shown in the model-visible environment context
/// for this turn and subsequent turns.
#[ts(optional = nullable)]
pub current_date: Option<String>,
/// Override the timezone shown in the model-visible environment context for
/// this turn and subsequent turns.
#[ts(optional = nullable)]
pub timezone: Option<String>,
/// Override the approval policy for this turn and subsequent turns.
#[experimental(nested)]
#[ts(optional = nullable)]

View File

@@ -658,6 +658,8 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
}],
responsesapi_client_metadata: None,
cwd: None,
current_date: None,
timezone: None,
approval_policy: None,
sandbox_policy: None,
permissions: None,

View File

@@ -806,6 +806,8 @@ impl ThreadRequestProcessor {
service_name,
base_instructions,
developer_instructions,
current_date,
timezone,
dynamic_tools,
mock_experimental_field: _mock_experimental_field,
experimental_raw_events,
@@ -837,6 +839,8 @@ impl ThreadRequestProcessor {
permissions,
base_instructions,
developer_instructions,
current_date,
timezone,
personality,
);
typesafe_overrides.ephemeral = ephemeral;
@@ -1201,6 +1205,8 @@ impl ThreadRequestProcessor {
permissions: Option<PermissionProfileSelectionParams>,
base_instructions: Option<String>,
developer_instructions: Option<String>,
current_date: Option<String>,
timezone: Option<String>,
personality: Option<Personality>,
) -> ConfigOverrides {
let mut overrides = ConfigOverrides {
@@ -1217,6 +1223,8 @@ impl ThreadRequestProcessor {
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
base_instructions,
developer_instructions,
current_date,
timezone,
personality,
..Default::default()
};
@@ -2369,6 +2377,8 @@ impl ThreadRequestProcessor {
config: mut request_overrides,
base_instructions,
developer_instructions,
current_date,
timezone,
personality,
exclude_turns,
persist_extended_history: _persist_extended_history,
@@ -2403,6 +2413,8 @@ impl ThreadRequestProcessor {
permissions,
base_instructions,
developer_instructions,
current_date,
timezone,
personality,
);
self.load_and_apply_persisted_resume_metadata(
@@ -3063,6 +3075,8 @@ impl ThreadRequestProcessor {
permissions,
base_instructions,
developer_instructions,
/*current_date*/ None,
/*timezone*/ None,
/*personality*/ None,
);
typesafe_overrides.ephemeral = ephemeral.then_some(true);

View File

@@ -637,6 +637,8 @@ mod thread_processor_behavior_tests {
config: None,
base_instructions: None,
developer_instructions: None,
current_date: None,
timezone: None,
personality: None,
exclude_turns: false,
persist_extended_history: false,

View File

@@ -354,6 +354,8 @@ impl TurnRequestProcessor {
let turn_has_input = !mapped_items.is_empty();
let has_any_overrides = params.cwd.is_some()
|| params.current_date.is_some()
|| params.timezone.is_some()
|| params.approval_policy.is_some()
|| params.approvals_reviewer.is_some()
|| params.sandbox_policy.is_some()
@@ -372,6 +374,8 @@ impl TurnRequestProcessor {
}
let cwd = params.cwd;
let current_date = params.current_date;
let timezone = params.timezone;
let approval_policy = params.approval_policy.map(AskForApproval::to_core);
let approvals_reviewer = params
.approvals_reviewer
@@ -429,6 +433,8 @@ impl TurnRequestProcessor {
thread
.validate_turn_context_overrides(CodexThreadTurnContextOverrides {
cwd: cwd.clone(),
current_date: current_date.clone(),
timezone: timezone.clone(),
approval_policy,
approvals_reviewer,
sandbox_policy: sandbox_policy.clone(),
@@ -454,6 +460,8 @@ impl TurnRequestProcessor {
final_output_json_schema: params.output_schema,
responsesapi_client_metadata: params.responsesapi_client_metadata,
cwd,
current_date,
timezone,
approval_policy,
approvals_reviewer,
sandbox_policy,

View File

@@ -592,6 +592,8 @@ async fn skills_changed_notification_is_emitted_after_skill_change() -> Result<(
service_name: None,
base_instructions: None,
developer_instructions: None,
current_date: None,
timezone: None,
personality: None,
ephemeral: None,
session_start_source: None,

View File

@@ -1890,6 +1890,8 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
}],
responsesapi_client_metadata: None,
cwd: Some(first_cwd.clone()),
current_date: None,
timezone: None,
approval_policy: Some(codex_app_server_protocol::AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
@@ -1931,6 +1933,8 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
}],
responsesapi_client_metadata: None,
cwd: Some(second_cwd.clone()),
current_date: None,
timezone: None,
approval_policy: Some(codex_app_server_protocol::AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::DangerFullAccess),

View File

@@ -81,6 +81,8 @@ impl ThreadConfigSnapshot {
#[derive(Clone, Default)]
pub struct CodexThreadTurnContextOverrides {
pub cwd: Option<PathBuf>,
pub current_date: Option<String>,
pub timezone: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub approvals_reviewer: Option<ApprovalsReviewer>,
pub sandbox_policy: Option<SandboxPolicy>,
@@ -240,6 +242,8 @@ impl CodexThread {
) -> ConstraintResult<()> {
let CodexThreadTurnContextOverrides {
cwd,
current_date,
timezone,
approval_policy,
approvals_reviewer,
sandbox_policy,
@@ -265,6 +269,8 @@ impl CodexThread {
let updates = SessionSettingsUpdate {
cwd,
current_date,
timezone,
approval_policy,
approvals_reviewer,
sandbox_policy,

View File

@@ -7043,6 +7043,8 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
current_date: None,
timezone: None,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
@@ -7463,6 +7465,8 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
current_date: None,
timezone: None,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
@@ -7621,6 +7625,8 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
current_date: None,
timezone: None,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
@@ -7764,6 +7770,8 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
current_date: None,
timezone: None,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,

View File

@@ -449,6 +449,14 @@ pub struct Config {
/// Developer instructions override injected as a separate message.
pub developer_instructions: Option<String>,
/// Optional current date override shown in the model-visible environment
/// context for this session.
pub current_date: Option<String>,
/// Optional timezone override shown in the model-visible environment
/// context for this session.
pub timezone: Option<String>,
/// Guardian-specific policy config override from requirements.toml or config.toml.
/// This is inserted into the fixed guardian prompt template under the
/// `# Policy Configuration` section rather than replacing the whole
@@ -1867,6 +1875,8 @@ pub struct ConfigOverrides {
pub zsh_path: Option<PathBuf>,
pub base_instructions: Option<String>,
pub developer_instructions: Option<String>,
pub current_date: Option<String>,
pub timezone: Option<String>,
pub personality: Option<Personality>,
pub compact_prompt: Option<String>,
pub include_apply_patch_tool: Option<bool>,
@@ -2142,6 +2152,8 @@ impl Config {
zsh_path: zsh_path_override,
base_instructions,
developer_instructions,
current_date,
timezone,
personality,
compact_prompt,
include_apply_patch_tool: include_apply_patch_tool_override,
@@ -2999,6 +3011,8 @@ impl Config {
base_instructions,
personality,
developer_instructions,
current_date,
timezone,
compact_prompt,
commit_attribution,
include_permissions_instructions,

View File

@@ -69,6 +69,21 @@ fn build_permissions_update_item(
)
}
fn build_developer_instructions_update_item(
previous: Option<&TurnContextItem>,
next: &TurnContext,
) -> Option<String> {
let prev = previous?;
if prev.developer_instructions == next.developer_instructions {
return None;
}
next.developer_instructions
.as_deref()
.filter(|instructions| !instructions.trim().is_empty())
.map(str::to_string)
}
fn build_collaboration_mode_update_item(
previous: Option<&TurnContextItem>,
next: &TurnContext,
@@ -218,6 +233,7 @@ pub(crate) fn build_settings_update_items(
// Keep model-switch instructions first so model-specific guidance is read before
// any other context diffs on this turn.
build_model_instructions_update_item(previous_turn_settings, next),
build_developer_instructions_update_item(previous, next),
build_permissions_update_item(previous, next, exec_policy),
build_collaboration_mode_update_item(previous, next),
build_realtime_update_item(previous, previous_turn_settings, next),

View File

@@ -143,6 +143,8 @@ pub(super) async fn user_input_or_turn_inner(
items,
SessionSettingsUpdate {
cwd: Some(cwd),
current_date: None,
timezone: None,
approval_policy: Some(approval_policy),
approvals_reviewer,
sandbox_policy: Some(sandbox_policy),
@@ -163,6 +165,8 @@ pub(super) async fn user_input_or_turn_inner(
}
Op::UserInputWithTurnContext {
cwd,
current_date,
timezone,
approval_policy,
approvals_reviewer,
sandbox_policy,
@@ -195,6 +199,8 @@ pub(super) async fn user_input_or_turn_inner(
items,
SessionSettingsUpdate {
cwd,
current_date,
timezone,
approval_policy,
approvals_reviewer,
sandbox_policy,

View File

@@ -620,6 +620,8 @@ impl Codex {
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
environments: environment_selections.to_selections(),

View File

@@ -75,6 +75,10 @@ pub(crate) struct SessionConfiguration {
/// execution sandbox are resolved against this directory **instead** of
/// the process-wide current working directory.
pub(super) cwd: AbsolutePathBuf,
/// Optional current date override for the model-visible environment context.
pub(super) current_date: Option<String>,
/// Optional timezone override for the model-visible environment context.
pub(super) timezone: Option<String>,
/// Directory containing all Codex state for this session.
pub(super) codex_home: AbsolutePathBuf,
/// Optional user-facing name for the thread, updated during the session.
@@ -199,6 +203,12 @@ impl SessionConfiguration {
if let Some(approval_policy) = updates.approval_policy {
next_configuration.approval_policy.set(approval_policy)?;
}
if let Some(current_date) = updates.current_date.clone() {
next_configuration.current_date = Some(current_date);
}
if let Some(timezone) = updates.timezone.clone() {
next_configuration.timezone = Some(timezone);
}
if let Some(approvals_reviewer) = updates.approvals_reviewer {
next_configuration.approvals_reviewer = approvals_reviewer;
}
@@ -309,6 +319,8 @@ impl SessionConfiguration {
#[derive(Default, Clone)]
pub(crate) struct SessionSettingsUpdate {
pub(crate) cwd: Option<PathBuf>,
pub(crate) current_date: Option<String>,
pub(crate) timezone: Option<String>,
pub(crate) approval_policy: Option<AskForApproval>,
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
pub(crate) sandbox_policy: Option<SandboxPolicy>,

View File

@@ -2581,6 +2581,8 @@ async fn set_rate_limits_retains_previous_credits() {
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
environments: Vec::new(),
@@ -2685,6 +2687,8 @@ async fn set_rate_limits_updates_plan_type_when_present() {
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
environments: Vec::new(),
@@ -3126,6 +3130,41 @@ async fn session_settings_legacy_fast_service_tier_update_uses_priority_request_
);
}
#[tokio::test]
async fn session_configuration_apply_updates_environment_context_overrides() {
let session_configuration = make_session_configuration_for_tests().await;
let updated = session_configuration
.apply(&SessionSettingsUpdate {
current_date: Some("2026-05-13".to_string()),
timezone: Some("America/Los_Angeles".to_string()),
..Default::default()
})
.expect("time context update should succeed");
assert_eq!(updated.current_date.as_deref(), Some("2026-05-13"));
assert_eq!(updated.timezone.as_deref(), Some("America/Los_Angeles"));
}
#[tokio::test]
async fn turn_context_uses_initial_environment_context_overrides() {
let (_session, turn_context, _rx) = make_session_and_context_with_auth_and_config_and_rx(
CodexAuth::from_api_key("Test API Key"),
Vec::new(),
|config| {
config.current_date = Some("2026-05-13".to_string());
config.timezone = Some("America/Los_Angeles".to_string());
},
)
.await;
assert_eq!(turn_context.current_date.as_deref(), Some("2026-05-13"));
assert_eq!(
turn_context.timezone.as_deref(),
Some("America/Los_Angeles")
);
}
pub(crate) async fn make_session_configuration_for_tests() -> SessionConfiguration {
let codex_home = tempfile::tempdir().expect("create temp dir");
let config = build_test_config(codex_home.path()).await;
@@ -3162,6 +3201,8 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
environments: Vec::new(),
@@ -3688,6 +3729,8 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
environments: Vec::new(),
@@ -3797,6 +3840,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
environments: default_environments,
@@ -4026,6 +4071,8 @@ async fn make_session_with_config_and_rx(
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: None,
timezone: None,
codex_home: config.codex_home.clone(),
thread_name: None,
environments: default_environments,
@@ -4129,6 +4176,8 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: None,
timezone: None,
codex_home: config.codex_home.clone(),
thread_name: None,
environments: default_environments,
@@ -4776,6 +4825,8 @@ fn op_kind_distinguishes_turn_ops() {
final_output_json_schema: None,
responsesapi_client_metadata: None,
cwd: None,
current_date: None,
timezone: None,
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
@@ -5516,6 +5567,8 @@ where
active_permission_profile: config.permissions.active_permission_profile(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
current_date: None,
timezone: None,
codex_home: config.codex_home.clone(),
thread_name: None,
environments: default_environments,
@@ -6004,6 +6057,63 @@ async fn build_settings_update_items_omits_environment_item_when_disabled() {
);
}
#[tokio::test]
async fn build_settings_update_items_emits_developer_item_for_developer_instruction_changes() {
let (session, mut previous_context) = make_session_and_context().await;
previous_context.developer_instructions = Some("old developer guidance".to_string());
let previous_context = Arc::new(previous_context);
let mut current_context = previous_context
.with_model(
previous_context.model_info.slug.clone(),
&session.services.models_manager,
)
.await;
current_context.developer_instructions = Some("new developer guidance".to_string());
let update_items = session
.build_settings_update_items(
Some(&previous_context.to_turn_context_item()),
&current_context,
)
.await;
let developer_texts = developer_input_texts(&update_items);
assert!(
developer_texts
.iter()
.any(|text| text.contains("new developer guidance")),
"expected changed developer instructions to be appended, got {developer_texts:?}"
);
assert!(
!developer_texts
.iter()
.any(|text| text.contains("old developer guidance")),
"did not expect previous developer instructions to be re-appended, got {developer_texts:?}"
);
}
#[tokio::test]
async fn build_settings_update_items_omits_unchanged_developer_instruction_item() {
let (session, mut previous_context) = make_session_and_context().await;
previous_context.developer_instructions = Some("stable developer guidance".to_string());
let previous_context = Arc::new(previous_context);
let current_context = previous_context
.with_model(
previous_context.model_info.slug.clone(),
&session.services.models_manager,
)
.await;
let update_items = session
.build_settings_update_items(
Some(&previous_context.to_turn_context_item()),
&current_context,
)
.await;
assert!(developer_input_texts(&update_items).is_empty());
}
#[tokio::test]
async fn build_settings_update_items_emits_realtime_start_when_session_becomes_live() {
let (session, previous_context) = make_session_and_context().await;

View File

@@ -536,7 +536,15 @@ impl Session {
session_configuration.windows_sandbox_level,
network.is_some(),
));
let (current_date, timezone) = local_time_context();
let (local_current_date, local_timezone) = local_time_context();
let current_date = session_configuration
.current_date
.clone()
.unwrap_or(local_current_date);
let timezone = session_configuration
.timezone
.clone()
.unwrap_or(local_timezone);
TurnContext {
sub_id,
trace_id: current_span_trace_id(),

View File

@@ -951,6 +951,8 @@ fn thread_start_params_from_config(config: &Config) -> ThreadStartParams {
sandbox: sandbox.flatten(),
permissions,
config: config_request_overrides_from_config(config),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
ephemeral: Some(config.ephemeral),
..ThreadStartParams::default()
}
@@ -973,6 +975,8 @@ fn thread_resume_params_from_config(config: &Config, thread_id: String) -> Threa
approvals_reviewer: approvals_reviewer_override_from_config(config),
sandbox: sandbox.flatten(),
permissions,
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
config: config_request_overrides_from_config(config),
..ThreadResumeParams::default()
}

View File

@@ -462,6 +462,14 @@ pub enum Op {
#[serde(skip_serializing_if = "Option::is_none")]
cwd: Option<PathBuf>,
/// Updated current date shown in the model-visible environment context.
#[serde(skip_serializing_if = "Option::is_none")]
current_date: Option<String>,
/// Updated timezone shown in the model-visible environment context.
#[serde(skip_serializing_if = "Option::is_none")]
timezone: Option<String>,
/// Updated command approval policy.
#[serde(skip_serializing_if = "Option::is_none")]
approval_policy: Option<AskForApproval>,

View File

@@ -1194,6 +1194,8 @@ fn thread_start_params_from_config(
sandbox,
permissions,
config: config_request_overrides_from_config(config),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
ephemeral: Some(config.ephemeral),
session_start_source,
thread_source: Some(ThreadSource::User),
@@ -1228,6 +1230,8 @@ fn thread_resume_params_from_config(
sandbox,
permissions,
config: config_request_overrides_from_config(&config),
current_date: config.current_date.clone(),
timezone: config.timezone.clone(),
persist_extended_history: false,
..ThreadResumeParams::default()
}