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
1 change: 1 addition & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ icu_locale_core = "2.1"
icu_provider = { version = "2.1", features = ["sync"] }
ignore = "0.4.23"
image = { version = "^0.25.9", default-features = false }
iana-time-zone = "0.1.64"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a huge dep?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex says:

• No. In this workspace it is not a huge dependency.

iana-time-zone = "0.1.64" is a small platform abstraction crate for getting the local IANA time zone. Also, that manifest entry is a semver range, so the lockfile currently resolves it to 0.1.65, not exactly 0.1.64.

What matters here:

  • On this macOS workspace, cargo tree -p iana-time-zone shows it resolves to just core-foundation-sys.
  • The lockfile lists Android/wasm/Windows helpers too, but those are target-specific, not all compiled on this platform.
  • It is already in the graph via chrono, so in this repo the incremental cost is effectively negligible.

include_dir = "0.7.4"
indexmap = "2.12.0"
insta = "1.46.3"
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/app-server/tests/suite/send_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,8 @@ fn append_rollout_turn_context(path: &Path, timestamp: &str, model: &str) -> std
item: RolloutItem::TurnContext(TurnContextItem {
turn_id: None,
cwd: PathBuf::from("/"),
current_date: None,
timezone: None,
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::DangerFullAccess,
network: None,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ env-flags = { workspace = true }
eventsource-stream = { workspace = true }
futures = { workspace = true }
http = { workspace = true }
iana-time-zone = { workspace = true }
indexmap = { workspace = true }
keyring = { workspace = true, features = ["crypto-rust"] }
libc = { workspace = true }
Expand Down
70 changes: 69 additions & 1 deletion codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ use crate::util::error_or_panic;
use crate::ws_version_from_features;
use async_channel::Receiver;
use async_channel::Sender;
use chrono::Local;
use chrono::Utc;
use codex_hooks::HookEvent;
use codex_hooks::HookEventAfterAgent;
use codex_hooks::HookPayload;
Expand Down Expand Up @@ -595,6 +597,8 @@ pub(crate) struct TurnContext {
/// the model as well as sandbox policies are resolved against this path
/// instead of `std::env::current_dir()`.
pub(crate) cwd: PathBuf,
pub(crate) current_date: Option<String>,
pub(crate) timezone: Option<String>,
pub(crate) developer_instructions: Option<String>,
pub(crate) compact_prompt: Option<String>,
pub(crate) user_instructions: Option<String>,
Expand Down Expand Up @@ -679,6 +683,8 @@ impl TurnContext {
reasoning_summary: self.reasoning_summary,
session_source: self.session_source.clone(),
cwd: self.cwd.clone(),
current_date: self.current_date.clone(),
timezone: self.timezone.clone(),
developer_instructions: self.developer_instructions.clone(),
compact_prompt: self.compact_prompt.clone(),
user_instructions: self.user_instructions.clone(),
Expand Down Expand Up @@ -719,6 +725,8 @@ impl TurnContext {
TurnContextItem {
turn_id: Some(self.sub_id.clone()),
cwd: self.cwd.clone(),
current_date: self.current_date.clone(),
timezone: self.timezone.clone(),
approval_policy: self.approval_policy.value(),
sandbox_policy: self.sandbox_policy.get().clone(),
network: self.turn_context_network_item(),
Expand Down Expand Up @@ -748,6 +756,16 @@ impl TurnContext {
}
}

fn local_time_context() -> (String, String) {
match iana_time_zone::get_timezone() {
Ok(timezone) => (Local::now().format("%Y-%m-%d").to_string(), timezone),
Err(_) => (
Utc::now().format("%Y-%m-%d").to_string(),
"Etc/UTC".to_string(),
),
}
}

#[derive(Clone)]
pub(crate) struct SessionConfiguration {
/// Provider identifier ("openai", "openrouter", ...).
Expand Down Expand Up @@ -1017,6 +1035,7 @@ impl Session {
.features
.enabled(Feature::UseLinuxSandboxBwrap),
));
let (current_date, timezone) = local_time_context();
TurnContext {
sub_id,
config: per_turn_config.clone(),
Expand All @@ -1028,6 +1047,8 @@ impl Session {
reasoning_summary,
session_source,
cwd,
current_date: Some(current_date),
timezone: Some(timezone),
developer_instructions: session_configuration.developer_instructions.clone(),
compact_prompt: session_configuration.compact_prompt.clone(),
user_instructions: session_configuration.user_instructions.clone(),
Expand Down Expand Up @@ -4676,6 +4697,8 @@ async fn spawn_review_thread(
tools_config,
features: parent_turn_context.features.clone(),
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
current_date: parent_turn_context.current_date.clone(),
timezone: parent_turn_context.timezone.clone(),
developer_instructions: None,
user_instructions: None,
compact_prompt: parent_turn_context.compact_prompt.clone(),
Expand Down Expand Up @@ -7259,6 +7282,8 @@ mod tests {
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
cwd: turn_context.cwd.clone(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
network: None,
Expand Down Expand Up @@ -7296,6 +7321,8 @@ mod tests {
let mut previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
cwd: turn_context.cwd.clone(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
network: None,
Expand Down Expand Up @@ -7490,7 +7517,10 @@ mod tests {
.record_context_updates_and_set_reference_context_item(&turn_context, None)
.await;
let history_after_second_seed = session.clone_history().await;
assert_eq!(expected, history_after_second_seed.raw_items());
assert_eq!(
history_after_seed.raw_items(),
history_after_second_seed.raw_items()
);
}

#[tokio::test]
Expand Down Expand Up @@ -7658,6 +7688,8 @@ mod tests {
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
cwd: turn_context.cwd.clone(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
network: None,
Expand Down Expand Up @@ -8823,6 +8855,42 @@ mod tests {
assert!(environment_update.contains("<denied>blocked.example.com</denied>"));
}

#[tokio::test]
async fn build_settings_update_items_emits_environment_item_for_time_changes() {
let (session, previous_context) = make_session_and_context().await;
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.current_date = Some("2026-02-27".to_string());
current_context.timezone = Some("Europe/Berlin".to_string());

let reference_context_item = previous_context.to_turn_context_item();
let update_items = session.build_settings_update_items(
Some(&reference_context_item),
None,
&current_context,
);

let environment_update = update_items
.iter()
.find_map(|item| match item {
ResponseItem::Message { role, content, .. } if role == "user" => {
let [ContentItem::InputText { text }] = content.as_slice() else {
return None;
};
text.contains("<environment_context>").then_some(text)
}
_ => None,
})
.expect("environment update item should be emitted");
assert!(environment_update.contains("<current_date>2026-02-27</current_date>"));
assert!(environment_update.contains("<timezone>Europe/Berlin</timezone>"));
}

#[tokio::test]
async fn record_context_updates_and_set_reference_context_item_injects_full_context_when_baseline_missing()
{
Expand Down
Loading
Loading