[codex] add clock current-time tool (#29011)

## Summary
- expose `clock.curr_time` when current-time reminders are enabled
- query the session's configured time provider with the calling thread
id
- return the existing UTC reminder text for direct model calls
- return `{ "current_time": "YYYY-MM-DD HH:MM:SS UTC" }` in Code Mode

Clock lookup failures remain fatal, matching pre-inference reminder
behavior.

## Testing
- `just test -p codex-core current_time_tool_returns_the_latest_time`
- `just test -p codex-core
code_mode_current_time_returns_structured_result`
- `just fix -p codex-core`
This commit is contained in:
rka-oai
2026-06-19 01:46:57 +00:00
committed by GitHub
parent 38c96866f0
commit 73251b2f00
6 changed files with 210 additions and 4 deletions
@@ -11,6 +11,12 @@ impl CurrentTimeReminder {
pub(crate) fn new(current_time: DateTime<Utc>) -> Self {
Self { current_time }
}
pub(crate) fn formatted_time(&self) -> String {
self.current_time
.format("%Y-%m-%d %H:%M:%S UTC")
.to_string()
}
}
impl ContextualUserFragment for CurrentTimeReminder {
@@ -27,9 +33,6 @@ impl ContextualUserFragment for CurrentTimeReminder {
}
fn body(&self) -> String {
format!(
"It is {}.",
self.current_time.format("%Y-%m-%d %H:%M:%S UTC")
)
format!("It is {}.", self.formatted_time())
}
}
@@ -0,0 +1,108 @@
use crate::context::ContextualUserFragment;
use crate::context::CurrentTimeReminder;
use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor;
use codex_protocol::models::ResponseInputItem;
use codex_tools::JsonSchema;
use codex_tools::ResponsesApiNamespace;
use codex_tools::ResponsesApiNamespaceTool;
use codex_tools::ResponsesApiTool;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
use serde_json::Value as JsonValue;
use serde_json::json;
use std::collections::BTreeMap;
const NAMESPACE: &str = "clock";
const TOOL_NAME: &str = "curr_time";
struct CurrentTimeOutput(CurrentTimeReminder);
impl ToolOutput for CurrentTimeOutput {
fn log_preview(&self) -> String {
self.0.render()
}
fn success_for_logging(&self) -> bool {
true
}
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
FunctionToolOutput::from_text(self.0.render(), Some(true))
.to_response_item(call_id, payload)
}
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue {
json!({
"current_time": self.0.formatted_time(),
})
}
}
pub struct CurrentTimeHandler;
impl ToolExecutor<ToolInvocation> for CurrentTimeHandler {
fn tool_name(&self) -> ToolName {
ToolName::namespaced(NAMESPACE, TOOL_NAME)
}
fn spec(&self) -> ToolSpec {
ToolSpec::Namespace(ResponsesApiNamespace {
name: NAMESPACE.to_string(),
description: "Tools for reading the current time.".to_string(),
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
name: TOOL_NAME.to_string(),
description: "Return the current time in UTC.".to_string(),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(
BTreeMap::new(),
/*required*/ None,
/*additional_properties*/ Some(false.into()),
),
output_schema: Some(json!({
"type": "object",
"properties": {
"current_time": {
"type": "string",
"description": "Current UTC time formatted as YYYY-MM-DD HH:MM:SS UTC."
}
},
"required": ["current_time"],
"additionalProperties": false
})),
})],
})
}
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> {
Box::pin(async move {
if !matches!(invocation.payload, ToolPayload::Function { .. }) {
return Err(FunctionCallError::RespondToModel(format!(
"{TOOL_NAME} handler received unsupported payload"
)));
}
let current_time = invocation
.session
.services
.time_provider
.current_time(invocation.session.thread_id)
.await
.map_err(|err| {
FunctionCallError::Fatal(format!("failed to read current time: {err:#}"))
})?;
Ok(boxed_tool_output(CurrentTimeOutput(
CurrentTimeReminder::new(current_time),
)))
})
}
}
impl CoreToolRuntime for CurrentTimeHandler {}
+2
View File
@@ -2,6 +2,7 @@ pub(crate) mod agent_jobs;
pub(crate) mod agent_jobs_spec;
pub(crate) mod apply_patch;
pub(crate) mod apply_patch_spec;
mod current_time;
mod dynamic;
pub(crate) mod extension_tools;
mod get_context_remaining;
@@ -55,6 +56,7 @@ pub(crate) use crate::tools::code_mode::CodeModeWaitHandler;
pub use apply_patch::ApplyPatchHandler;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::AskForApproval;
pub use current_time::CurrentTimeHandler;
pub use dynamic::DynamicToolHandler;
pub use get_context_remaining::GetContextRemainingHandler;
pub use list_available_plugins_to_install::ListAvailablePluginsToInstallHandler;
+5
View File
@@ -7,6 +7,7 @@ use crate::tools::effective_tool_mode;
use crate::tools::handlers::ApplyPatchHandler;
use crate::tools::handlers::CodeModeExecuteHandler;
use crate::tools::handlers::CodeModeWaitHandler;
use crate::tools::handlers::CurrentTimeHandler;
use crate::tools::handlers::DynamicToolHandler;
use crate::tools::handlers::ExecCommandHandler;
use crate::tools::handlers::ExecCommandHandlerOptions;
@@ -711,6 +712,10 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut
planned_tools.add(GetContextRemainingHandler);
}
if features.enabled(Feature::CurrentTimeReminder) {
planned_tools.add(CurrentTimeHandler);
}
if features.enabled(Feature::SleepTool) {
planned_tools.add(SleepHandler);
}