Skip to main content

everruns_core/capabilities/
current_time.rs

1//! CurrentTime Capability - provides tools to get current date and time
2
3use super::{Capability, CapabilityLocalization, CapabilityStatus, Fact, FactsContext};
4use crate::tool_types::ToolHints;
5use crate::tools::{Tool, ToolExecutionResult};
6use async_trait::async_trait;
7use chrono::SecondsFormat;
8use serde_json::Value;
9
10pub const CURRENT_TIME_CAPABILITY_ID: &str = "current_time";
11
12/// CurrentTime capability - provides tools to get current date and time
13pub struct CurrentTimeCapability;
14
15impl Capability for CurrentTimeCapability {
16    fn id(&self) -> &str {
17        CURRENT_TIME_CAPABILITY_ID
18    }
19
20    fn name(&self) -> &str {
21        "Current Time"
22    }
23
24    fn description(&self) -> &str {
25        "Adds a tool to get the current date and time in various formats and timezones."
26    }
27
28    fn localizations(&self) -> Vec<CapabilityLocalization> {
29        vec![CapabilityLocalization::text(
30            "uk",
31            "Поточний час",
32            "Додає інструмент для отримання поточної дати й часу в різних форматах і часових поясах.",
33        )]
34    }
35
36    fn status(&self) -> CapabilityStatus {
37        CapabilityStatus::Available
38    }
39
40    fn icon(&self) -> Option<&str> {
41        Some("clock")
42    }
43
44    fn category(&self) -> Option<&str> {
45        Some("Core")
46    }
47
48    fn tools(&self) -> Vec<Box<dyn Tool>> {
49        vec![Box::new(GetCurrentTimeTool)]
50    }
51
52    /// Contribute the current UTC time as a dynamic fact. The runtime appends
53    /// it to a live `<facts>` block at the conversation tail each turn, so the
54    /// model always knows "now" without a tool round-trip and without the
55    /// changing value invalidating the system-prompt cache. The
56    /// `get_current_time` tool remains for explicit timezone/format queries.
57    fn facts(&self, _config: &Value, _ctx: &FactsContext) -> Vec<Fact> {
58        let now = chrono::Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
59        vec![Fact::dynamic("current_time", now)]
60    }
61}
62
63// ============================================================================
64// Tool: get_current_time
65// ============================================================================
66
67/// Tool that returns the current date and time
68pub struct GetCurrentTimeTool;
69
70#[async_trait]
71impl Tool for GetCurrentTimeTool {
72    fn name(&self) -> &str {
73        "get_current_time"
74    }
75
76    fn display_name(&self) -> Option<&str> {
77        Some("Get Current Time")
78    }
79
80    fn description(&self) -> &str {
81        "Get the current date and time. Can return time in different formats and timezones."
82    }
83
84    fn parameters_schema(&self) -> Value {
85        serde_json::json!({
86            "type": "object",
87            "properties": {
88                "timezone": {
89                    "type": "string",
90                    "description": "Timezone to return the time in (e.g., 'UTC', 'America/New_York', 'Europe/London'). Defaults to UTC."
91                },
92                "format": {
93                    "type": "string",
94                    "enum": ["iso8601", "unix", "human"],
95                    "description": "Output format: 'iso8601' for ISO 8601 format, 'unix' for Unix timestamp, 'human' for human-readable format. Defaults to 'iso8601'."
96                }
97            },
98            "additionalProperties": false
99        })
100    }
101
102    fn hints(&self) -> ToolHints {
103        ToolHints::default()
104            .with_readonly(true)
105            .with_idempotent(true)
106    }
107
108    async fn execute(&self, arguments: Value) -> ToolExecutionResult {
109        let format = arguments
110            .get("format")
111            .and_then(|v| v.as_str())
112            .unwrap_or("iso8601");
113
114        let _timezone = arguments
115            .get("timezone")
116            .and_then(|v| v.as_str())
117            .unwrap_or("UTC");
118
119        // Note: For simplicity, we're using UTC. Full timezone support would require
120        // the chrono-tz crate which adds significant dependencies.
121        let now = chrono::Utc::now();
122
123        let result = match format {
124            "unix" => serde_json::json!({
125                "timestamp": now.timestamp(),
126                "format": "unix",
127                "timezone": "UTC"
128            }),
129            "human" => serde_json::json!({
130                "datetime": now.format("%A, %B %d, %Y at %H:%M:%S UTC").to_string(),
131                "format": "human",
132                "timezone": "UTC"
133            }),
134            _ => serde_json::json!({
135                "datetime": now.to_rfc3339(),
136                "format": "iso8601",
137                "timezone": "UTC"
138            }),
139        };
140
141        ToolExecutionResult::success(result)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    // Metadata/tool-list constants covered by builtin_capabilities_satisfy_registry_invariants.
150
151    #[test]
152    fn test_capability_no_system_prompt() {
153        let cap = CurrentTimeCapability;
154        assert!(cap.system_prompt_addition().is_none());
155    }
156
157    #[test]
158    fn test_contributes_dynamic_current_time_fact() {
159        use crate::capabilities::{FactsContext, Volatility};
160        use crate::typed_id::SessionId;
161
162        let cap = CurrentTimeCapability;
163        let facts = cap.facts(
164            &serde_json::Value::Null,
165            &FactsContext::new(SessionId::new()),
166        );
167        assert_eq!(facts.len(), 1);
168        assert_eq!(facts[0].key, "current_time");
169        assert_eq!(facts[0].volatility, Volatility::Dynamic);
170        // RFC3339 UTC, e.g. 2026-07-04T12:00:00Z
171        assert!(facts[0].value.ends_with('Z'), "got: {}", facts[0].value);
172        assert!(facts[0].value.contains('T'));
173    }
174
175    #[tokio::test]
176    async fn test_get_current_time_iso8601() {
177        let tool = GetCurrentTimeTool;
178        let result = tool.execute(serde_json::json!({})).await;
179
180        if let ToolExecutionResult::Success(value) = result {
181            assert!(value.get("datetime").is_some());
182            assert_eq!(value.get("format").unwrap().as_str().unwrap(), "iso8601");
183            assert_eq!(value.get("timezone").unwrap().as_str().unwrap(), "UTC");
184        } else {
185            panic!("Expected success");
186        }
187    }
188
189    #[tokio::test]
190    async fn test_get_current_time_unix() {
191        let tool = GetCurrentTimeTool;
192        let result = tool.execute(serde_json::json!({"format": "unix"})).await;
193
194        if let ToolExecutionResult::Success(value) = result {
195            assert!(value.get("timestamp").is_some());
196            assert_eq!(value.get("format").unwrap().as_str().unwrap(), "unix");
197        } else {
198            panic!("Expected success");
199        }
200    }
201
202    #[tokio::test]
203    async fn test_get_current_time_human() {
204        let tool = GetCurrentTimeTool;
205        let result = tool.execute(serde_json::json!({"format": "human"})).await;
206
207        if let ToolExecutionResult::Success(value) = result {
208            assert!(value.get("datetime").is_some());
209            assert_eq!(value.get("format").unwrap().as_str().unwrap(), "human");
210            let datetime = value.get("datetime").unwrap().as_str().unwrap();
211            assert!(datetime.contains("at"));
212        } else {
213            panic!("Expected success");
214        }
215    }
216}