everruns_core/capabilities/
current_time.rs1use 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
12pub 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 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
63pub struct GetCurrentTimeTool;
69
70#[async_trait]
71impl Tool for GetCurrentTimeTool {
72 fn narrate(
73 &self,
74 _tool_call: &crate::tool_types::ToolCall,
75 phase: crate::tool_narration::ToolNarrationPhase,
76 locale: Option<&str>,
77 _ctx: crate::tool_narration::ToolNarrationContext<'_>,
78 ) -> Option<String> {
79 Some(crate::tool_narration::narrate_current_time(phase, locale))
80 }
81
82 fn name(&self) -> &str {
83 "get_current_time"
84 }
85
86 fn display_name(&self) -> Option<&str> {
87 Some("Get Current Time")
88 }
89
90 fn description(&self) -> &str {
91 "Get the current date and time. Can return time in different formats and timezones."
92 }
93
94 fn parameters_schema(&self) -> Value {
95 serde_json::json!({
96 "type": "object",
97 "properties": {
98 "timezone": {
99 "type": "string",
100 "description": "Timezone to return the time in (e.g., 'UTC', 'America/New_York', 'Europe/London'). Defaults to UTC."
101 },
102 "format": {
103 "type": "string",
104 "enum": ["iso8601", "unix", "human"],
105 "description": "Output format: 'iso8601' for ISO 8601 format, 'unix' for Unix timestamp, 'human' for human-readable format. Defaults to 'iso8601'."
106 }
107 },
108 "additionalProperties": false
109 })
110 }
111
112 fn hints(&self) -> ToolHints {
113 ToolHints::default()
114 .with_readonly(true)
115 .with_idempotent(true)
116 }
117
118 async fn execute(&self, arguments: Value) -> ToolExecutionResult {
119 let format = arguments
120 .get("format")
121 .and_then(|v| v.as_str())
122 .unwrap_or("iso8601");
123
124 let _timezone = arguments
125 .get("timezone")
126 .and_then(|v| v.as_str())
127 .unwrap_or("UTC");
128
129 let now = chrono::Utc::now();
132
133 let result = match format {
134 "unix" => serde_json::json!({
135 "timestamp": now.timestamp(),
136 "format": "unix",
137 "timezone": "UTC"
138 }),
139 "human" => serde_json::json!({
140 "datetime": now.format("%A, %B %d, %Y at %H:%M:%S UTC").to_string(),
141 "format": "human",
142 "timezone": "UTC"
143 }),
144 _ => serde_json::json!({
145 "datetime": now.to_rfc3339(),
146 "format": "iso8601",
147 "timezone": "UTC"
148 }),
149 };
150
151 ToolExecutionResult::success(result)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
162 fn test_capability_no_system_prompt() {
163 let cap = CurrentTimeCapability;
164 assert!(cap.system_prompt_addition().is_none());
165 }
166
167 #[test]
168 fn test_contributes_dynamic_current_time_fact() {
169 use crate::capabilities::{FactsContext, Volatility};
170 use crate::typed_id::SessionId;
171
172 let cap = CurrentTimeCapability;
173 let facts = cap.facts(
174 &serde_json::Value::Null,
175 &FactsContext::new(SessionId::new()),
176 );
177 assert_eq!(facts.len(), 1);
178 assert_eq!(facts[0].key, "current_time");
179 assert_eq!(facts[0].volatility, Volatility::Dynamic);
180 assert!(facts[0].value.ends_with('Z'), "got: {}", facts[0].value);
182 assert!(facts[0].value.contains('T'));
183 }
184
185 #[tokio::test]
186 async fn test_get_current_time_iso8601() {
187 let tool = GetCurrentTimeTool;
188 let result = tool.execute(serde_json::json!({})).await;
189
190 if let ToolExecutionResult::Success(value) = result {
191 assert!(value.get("datetime").is_some());
192 assert_eq!(value.get("format").unwrap().as_str().unwrap(), "iso8601");
193 assert_eq!(value.get("timezone").unwrap().as_str().unwrap(), "UTC");
194 } else {
195 panic!("Expected success");
196 }
197 }
198
199 #[tokio::test]
200 async fn test_get_current_time_unix() {
201 let tool = GetCurrentTimeTool;
202 let result = tool.execute(serde_json::json!({"format": "unix"})).await;
203
204 if let ToolExecutionResult::Success(value) = result {
205 assert!(value.get("timestamp").is_some());
206 assert_eq!(value.get("format").unwrap().as_str().unwrap(), "unix");
207 } else {
208 panic!("Expected success");
209 }
210 }
211
212 #[tokio::test]
213 async fn test_get_current_time_human() {
214 let tool = GetCurrentTimeTool;
215 let result = tool.execute(serde_json::json!({"format": "human"})).await;
216
217 if let ToolExecutionResult::Success(value) = result {
218 assert!(value.get("datetime").is_some());
219 assert_eq!(value.get("format").unwrap().as_str().unwrap(), "human");
220 let datetime = value.get("datetime").unwrap().as_str().unwrap();
221 assert!(datetime.contains("at"));
222 } else {
223 panic!("Expected success");
224 }
225 }
226}