use chrono::Local;
use crate::session::Message;
pub fn format_message_envelope() -> String {
format!("[{}]", Local::now().format("%a %Y-%m-%d %H:%M %:z"))
}
const DEFAULT_SYSTEM_PROMPT: &str = r#"You are ZeptoClaw, an ultra-lightweight personal AI assistant.
You have access to tools to help accomplish tasks. Use them when needed.
Be concise but helpful. Focus on completing the user's request efficiently.
You have a longterm_memory tool. Use it proactively to:
- Save important facts, user preferences, and decisions for future recall
- Recall relevant information from past conversations by calling longterm_memory with action "search"
- Pin critical information that should always be available
## Scheduled & Background Messages
When a message begins with `Reminder:`, it was delivered by the scheduler on behalf of the user — not typed by them now. Respond with a friendly, concise notification of the reminder content, as if you're the reminder itself notifying the user.
When a message is the heartbeat prompt (checking workspace tasks), reply with `HEARTBEAT_OK` if there is nothing actionable to do, or take the requested action if there is."#;
#[allow(dead_code)]
pub const FIRST_RUN_PERSONA_PROMPT: &str = r#"
## First Conversation Setup
This appears to be a new chat. Take a moment to introduce yourself briefly and ask the user what kind of assistant they'd like you to be. Offer these options:
1. **Default** — balanced and helpful
2. **Concise** — short, direct answers
3. **Friendly** — warm and conversational
4. **Professional** — formal and structured
5. **Creative** — playful and imaginative
6. **Technical** — detailed expert mode
Say something like: "Hi! I'm your AI assistant. I can adapt my style to suit you. Would you like me to be concise, friendly, professional, creative, or technical? Or just say 'default' for a balanced approach. You can also describe any custom style you'd like!"
After the user responds, save their preference using longterm_memory with key "persona_pref:{chat_id}" and apply it going forward."#;
#[derive(Debug, Clone, Default)]
pub struct RuntimeContext {
pub channel: Option<String>,
pub available_tools: Vec<String>,
pub timezone: Option<String>,
pub workspace: Option<String>,
pub os_info: Option<String>,
}
impl RuntimeContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_channel(mut self, channel: &str) -> Self {
self.channel = Some(channel.to_string());
self
}
pub fn with_tools(mut self, tools: Vec<String>) -> Self {
self.available_tools = tools;
self
}
#[deprecated(note = "Use with_timezone() instead for live time computation")]
pub fn with_current_time(mut self) -> Self {
self.timezone = Some("UTC".to_string());
self
}
pub fn with_timezone(mut self, tz: &str) -> Self {
self.timezone = Some(tz.to_string());
self
}
pub fn with_workspace(mut self, workspace: &str) -> Self {
self.workspace = Some(workspace.to_string());
self
}
pub fn with_os_info(mut self) -> Self {
self.os_info = Some(format!(
"{} {}",
std::env::consts::OS,
std::env::consts::ARCH
));
self
}
pub fn is_empty(&self) -> bool {
self.channel.is_none()
&& self.available_tools.is_empty()
&& self.timezone.is_none()
&& self.workspace.is_none()
&& self.os_info.is_none()
}
pub fn render(&self) -> Option<String> {
if self.is_empty() {
return None;
}
let mut parts = Vec::new();
if let Some(ref channel) = self.channel {
parts.push(format!("- Channel: {}", channel));
}
if !self.available_tools.is_empty() {
parts.push(format!(
"- Available tools: {}",
self.available_tools.join(", ")
));
}
if self.timezone.is_some() {
let now = Local::now();
parts.push(format!(
"- Current time: {}",
now.format("%a %Y-%m-%d %H:%M %:z")
));
parts.push(format!("- Timezone: {}", now.format("%:z")));
}
if let Some(ref workspace) = self.workspace {
parts.push(format!("- Workspace: {}", workspace));
}
if let Some(ref os) = self.os_info {
parts.push(format!("- Platform: {}", os));
}
Some(format!("## Runtime Context\n\n{}", parts.join("\n")))
}
}
pub struct ContextBuilder {
system_prompt: String,
soul_prompt: Option<String>,
skills_prompt: Option<String>,
runtime_context: Option<RuntimeContext>,
memory_context: Option<String>,
}
impl ContextBuilder {
pub fn new() -> Self {
Self {
system_prompt: DEFAULT_SYSTEM_PROMPT.to_string(),
soul_prompt: None,
skills_prompt: None,
runtime_context: None,
memory_context: None,
}
}
pub fn with_system_prompt(mut self, prompt: &str) -> Self {
self.system_prompt = prompt.to_string();
self
}
pub fn with_soul(mut self, content: &str) -> Self {
self.soul_prompt = Some(content.to_string());
self
}
pub fn with_skills(mut self, skills_content: &str) -> Self {
self.skills_prompt = Some(skills_content.to_string());
self
}
pub fn with_runtime_context(mut self, ctx: RuntimeContext) -> Self {
if !ctx.is_empty() {
self.runtime_context = Some(ctx);
}
self
}
pub fn with_memory_context(mut self, memory_context: String) -> Self {
if !memory_context.is_empty() {
self.memory_context = Some(memory_context);
}
self
}
pub fn with_system_prompt_suffix(mut self, suffix: &str) -> Self {
self.system_prompt.push_str(suffix);
self
}
pub fn build_system_message(&self) -> Message {
let mut content = String::new();
if let Some(ref soul) = self.soul_prompt {
content.push_str(soul);
content.push_str("\n\n");
}
content.push_str(&self.system_prompt);
if let Some(ref skills) = self.skills_prompt {
content.push_str("\n\n## Available Skills\n\n");
content.push_str(skills);
}
if let Some(ref ctx) = self.runtime_context {
if let Some(rendered) = ctx.render() {
content.push_str("\n\n");
content.push_str(&rendered);
}
}
if let Some(ref mem) = self.memory_context {
content.push_str("\n\n");
content.push_str(mem);
}
Message::system(&content)
}
fn build_system_message_with_memory_override(&self, memory_override: Option<&str>) -> Message {
let mut content = String::new();
if let Some(ref soul) = self.soul_prompt {
content.push_str(soul);
content.push_str("\n\n");
}
content.push_str(&self.system_prompt);
if let Some(ref skills) = self.skills_prompt {
content.push_str("\n\n## Available Skills\n\n");
content.push_str(skills);
}
if let Some(ref ctx) = self.runtime_context {
if let Some(rendered) = ctx.render() {
content.push_str("\n\n");
content.push_str(&rendered);
}
}
let memory = match memory_override {
Some("") => None,
Some(memory) => Some(memory),
None => self.memory_context.as_deref(),
};
if let Some(memory) = memory {
content.push_str("\n\n");
content.push_str(memory);
}
Message::system(&content)
}
pub fn build_messages(&self, history: &[Message], user_input: &str) -> Vec<Message> {
let mut messages = vec![self.build_system_message()];
messages.extend(history.iter().cloned());
if !user_input.is_empty() {
let content = if let Some(ref ctx) = self.runtime_context {
if ctx.timezone.is_some() {
let envelope = format_message_envelope();
format!("{} {}", envelope, user_input)
} else {
user_input.to_string()
}
} else {
user_input.to_string()
};
messages.push(Message::user(&content));
}
messages
}
pub fn build_messages_with_memory_override(
&self,
history: &[Message],
user_input: &str,
memory_override: Option<&str>,
) -> Vec<Message> {
let mut messages = vec![self.build_system_message_with_memory_override(memory_override)];
messages.extend(history.iter().cloned());
if !user_input.is_empty() {
let content = if let Some(ref ctx) = self.runtime_context {
if ctx.timezone.is_some() {
let envelope = format_message_envelope();
format!("{} {}", envelope, user_input)
} else {
user_input.to_string()
}
} else {
user_input.to_string()
};
messages.push(Message::user(&content));
}
messages
}
pub fn system_prompt(&self) -> &str {
&self.system_prompt
}
pub fn has_soul(&self) -> bool {
self.soul_prompt.is_some()
}
pub fn has_skills(&self) -> bool {
self.skills_prompt.is_some()
}
}
impl Default for ContextBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::Role;
#[test]
fn test_context_builder_new() {
let builder = ContextBuilder::new();
assert!(builder.system_prompt().contains("ZeptoClaw"));
assert!(!builder.has_skills());
}
#[test]
fn test_context_builder_default() {
let builder = ContextBuilder::default();
assert!(builder.system_prompt().contains("ZeptoClaw"));
}
#[test]
fn test_context_builder_custom_system_prompt() {
let builder = ContextBuilder::new().with_system_prompt("Custom prompt here");
assert_eq!(builder.system_prompt(), "Custom prompt here");
}
#[test]
fn test_context_builder_with_skills() {
let builder = ContextBuilder::new().with_skills("- /test: Test skill");
assert!(builder.has_skills());
let system = builder.build_system_message();
assert!(system.content.contains("Available Skills"));
assert!(system.content.contains("/test"));
}
#[test]
fn test_build_system_message() {
let builder = ContextBuilder::new();
let system = builder.build_system_message();
assert_eq!(system.role, Role::System);
assert!(system.content.contains("ZeptoClaw"));
}
#[test]
fn test_build_messages_empty_input() {
let builder = ContextBuilder::new();
let messages = builder.build_messages(&[], "");
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].role, Role::System);
}
#[test]
fn test_build_messages_with_input() {
let builder = ContextBuilder::new();
let messages = builder.build_messages(&[], "Hello");
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].role, Role::System);
assert_eq!(messages[1].role, Role::User);
assert_eq!(messages[1].content, "Hello");
}
#[test]
fn test_build_messages_with_history() {
let builder = ContextBuilder::new();
let history = vec![
Message::user("Previous message"),
Message::assistant("Previous response"),
];
let messages = builder.build_messages(&history, "New message");
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].role, Role::System);
assert_eq!(messages[1].role, Role::User);
assert_eq!(messages[1].content, "Previous message");
assert_eq!(messages[2].role, Role::Assistant);
assert_eq!(messages[3].role, Role::User);
assert_eq!(messages[3].content, "New message");
}
#[test]
fn test_build_messages_preserves_history_order() {
let builder = ContextBuilder::new();
let history = vec![
Message::user("First"),
Message::assistant("Second"),
Message::user("Third"),
Message::assistant("Fourth"),
];
let messages = builder.build_messages(&history, "");
assert_eq!(messages.len(), 5);
assert_eq!(messages[1].content, "First");
assert_eq!(messages[2].content, "Second");
assert_eq!(messages[3].content, "Third");
assert_eq!(messages[4].content, "Fourth");
}
#[test]
fn test_context_builder_with_soul() {
let builder = ContextBuilder::new().with_soul("You are a pirate captain.");
assert!(builder.has_soul());
let system = builder.build_system_message();
assert!(system.content.starts_with("You are a pirate captain."));
assert!(system.content.contains("ZeptoClaw"));
}
#[test]
fn test_soul_prepended_before_system_prompt() {
let builder = ContextBuilder::new()
.with_soul("SOUL: Be kind.")
.with_system_prompt("SYSTEM: Do tasks.");
let system = builder.build_system_message();
let soul_pos = system.content.find("SOUL: Be kind.").unwrap();
let system_pos = system.content.find("SYSTEM: Do tasks.").unwrap();
assert!(soul_pos < system_pos);
}
#[test]
fn test_soul_with_skills() {
let builder = ContextBuilder::new()
.with_soul("Identity: helper")
.with_skills("- /test: Test");
let system = builder.build_system_message();
assert!(system.content.starts_with("Identity: helper"));
assert!(system.content.contains("ZeptoClaw"));
assert!(system.content.contains("Available Skills"));
assert!(system.content.contains("/test"));
}
#[test]
fn test_no_soul_by_default() {
let builder = ContextBuilder::new();
assert!(!builder.has_soul());
let system = builder.build_system_message();
assert!(system.content.starts_with("You are ZeptoClaw"));
}
#[test]
fn test_context_builder_chaining() {
let builder = ContextBuilder::new()
.with_system_prompt("Custom prompt")
.with_skills("- /skill1: Do something");
let system = builder.build_system_message();
assert!(system.content.contains("Custom prompt"));
assert!(system.content.contains("/skill1"));
}
#[test]
fn test_build_messages_with_tool_calls_in_history() {
use crate::session::ToolCall;
let builder = ContextBuilder::new();
let history = vec![
Message::user("Search for rust"),
Message::assistant_with_tools(
"Let me search for that.",
vec![ToolCall::new("call_1", "search", r#"{"q": "rust"}"#)],
),
Message::tool_result("call_1", "Found 100 results"),
Message::assistant("I found 100 results about Rust."),
];
let messages = builder.build_messages(&history, "Thanks!");
assert_eq!(messages.len(), 6);
assert!(messages[2].has_tool_calls());
assert!(messages[3].is_tool_result());
}
#[test]
fn test_runtime_context_empty() {
let ctx = RuntimeContext::new();
assert!(ctx.is_empty());
assert!(ctx.render().is_none());
}
#[test]
fn test_runtime_context_default() {
let ctx = RuntimeContext::default();
assert!(ctx.is_empty());
assert!(ctx.channel.is_none());
assert!(ctx.available_tools.is_empty());
assert!(ctx.timezone.is_none());
assert!(ctx.workspace.is_none());
assert!(ctx.os_info.is_none());
}
#[test]
fn test_runtime_context_with_channel() {
let ctx = RuntimeContext::new().with_channel("telegram");
assert!(!ctx.is_empty());
let rendered = ctx.render().unwrap();
assert!(rendered.contains("Channel: telegram"));
}
#[test]
fn test_runtime_context_with_tools() {
let ctx =
RuntimeContext::new().with_tools(vec!["shell".to_string(), "web_search".to_string()]);
assert!(!ctx.is_empty());
let rendered = ctx.render().unwrap();
assert!(rendered.contains("shell, web_search"));
}
#[test]
fn test_runtime_context_with_empty_tools() {
let ctx = RuntimeContext::new().with_tools(vec![]);
assert!(ctx.is_empty());
assert!(ctx.render().is_none());
}
#[test]
fn test_runtime_context_with_timezone() {
let ctx = RuntimeContext::new().with_timezone("Asia/Kuala_Lumpur");
assert!(!ctx.is_empty());
let rendered = ctx.render().unwrap();
assert!(rendered.contains("Current time:"));
assert!(
rendered.contains("Timezone: +") || rendered.contains("Timezone: -"),
"should show UTC offset: {}",
rendered
);
}
#[test]
fn test_runtime_context_with_utc_label() {
let ctx = RuntimeContext::new().with_timezone("UTC");
let rendered = ctx.render().unwrap();
assert!(rendered.contains("Current time:"));
assert!(
rendered.contains("Timezone: +") || rendered.contains("Timezone: -"),
"should show UTC offset: {}",
rendered
);
}
#[test]
fn test_runtime_context_time_format() {
let ctx = RuntimeContext::new().with_timezone("UTC");
let rendered = ctx.render().unwrap();
let time_line = rendered
.lines()
.find(|l| l.contains("Current time:"))
.unwrap();
assert!(
time_line.contains("202"),
"time should contain year: {}",
time_line
);
}
#[test]
fn test_runtime_context_time_is_live() {
let ctx = RuntimeContext::new().with_timezone("local");
let r1 = ctx.render().unwrap();
assert!(r1.contains("Current time:"));
}
#[test]
fn test_runtime_context_with_os_info() {
let ctx = RuntimeContext::new().with_os_info();
assert!(!ctx.is_empty());
let rendered = ctx.render().unwrap();
assert!(rendered.contains("Platform:"));
assert!(rendered.contains(std::env::consts::OS));
}
#[test]
fn test_runtime_context_with_workspace() {
let ctx = RuntimeContext::new().with_workspace("/home/user/project");
assert!(!ctx.is_empty());
let rendered = ctx.render().unwrap();
assert!(rendered.contains("Workspace: /home/user/project"));
}
#[test]
fn test_runtime_context_full() {
let ctx = RuntimeContext::new()
.with_channel("whatsapp")
.with_tools(vec!["shell".to_string()])
.with_workspace("/tmp/test")
.with_os_info();
let rendered = ctx.render().unwrap();
assert!(rendered.contains("## Runtime Context"));
assert!(rendered.contains("Channel: whatsapp"));
assert!(rendered.contains("Available tools: shell"));
assert!(rendered.contains("Workspace: /tmp/test"));
assert!(rendered.contains("Platform:"));
}
#[test]
fn test_runtime_context_render_ordering() {
let ctx = RuntimeContext::new()
.with_channel("cli")
.with_tools(vec!["echo".to_string()])
.with_workspace("/work");
let rendered = ctx.render().unwrap();
let channel_pos = rendered.find("Channel:").unwrap();
let tools_pos = rendered.find("Available tools:").unwrap();
let workspace_pos = rendered.find("Workspace:").unwrap();
assert!(channel_pos < tools_pos);
assert!(tools_pos < workspace_pos);
}
#[test]
fn test_runtime_context_clone() {
let ctx = RuntimeContext::new()
.with_channel("discord")
.with_workspace("/tmp");
let cloned = ctx.clone();
assert_eq!(ctx.channel, cloned.channel);
assert_eq!(ctx.workspace, cloned.workspace);
}
#[test]
fn test_message_envelope_format() {
let envelope = format_message_envelope();
assert!(envelope.starts_with('['));
assert!(envelope.ends_with(']'));
assert!(envelope.contains("202"), "should contain year");
assert!(
envelope.contains('+') || envelope.contains('-'),
"should contain UTC offset sign"
);
}
#[test]
fn test_message_envelope_contains_time_components() {
let envelope = format_message_envelope();
assert!(envelope.contains(':'), "should contain time separator");
assert!(envelope.len() > 10, "envelope should not be empty");
}
#[test]
fn test_build_messages_with_timezone_envelope() {
let ctx = RuntimeContext::new().with_timezone("UTC");
let builder = ContextBuilder::new().with_runtime_context(ctx);
let messages = builder.build_messages(&[], "Hello");
assert_eq!(messages.len(), 2);
assert!(messages[1].content.starts_with('['));
assert!(messages[1].content.contains("] Hello"));
}
#[test]
fn test_build_messages_without_timezone_no_envelope() {
let ctx = RuntimeContext::new().with_channel("cli");
let builder = ContextBuilder::new().with_runtime_context(ctx);
let messages = builder.build_messages(&[], "Hello");
assert_eq!(messages[1].content, "Hello");
}
#[test]
fn test_context_builder_with_runtime_context() {
let ctx = RuntimeContext::new().with_channel("discord");
let builder = ContextBuilder::new().with_runtime_context(ctx);
let system = builder.build_system_message();
assert!(system.content.contains("Runtime Context"));
assert!(system.content.contains("discord"));
}
#[test]
fn test_context_builder_empty_runtime_context_adds_nothing() {
let ctx = RuntimeContext::new();
let builder = ContextBuilder::new().with_runtime_context(ctx);
let system = builder.build_system_message();
assert!(!system.content.contains("Runtime Context"));
}
#[test]
fn test_context_builder_all_sections() {
let ctx = RuntimeContext::new().with_channel("cli");
let builder = ContextBuilder::new()
.with_skills("- /help: Show help")
.with_runtime_context(ctx);
let system = builder.build_system_message();
assert!(system.content.contains("ZeptoClaw"));
assert!(system.content.contains("Available Skills"));
assert!(system.content.contains("## Runtime Context"));
assert!(system.content.contains("cli"));
}
#[test]
fn test_context_builder_section_ordering() {
let ctx = RuntimeContext::new().with_channel("slack");
let builder = ContextBuilder::new()
.with_skills("- /deploy: Deploy app")
.with_runtime_context(ctx);
let system = builder.build_system_message();
let skills_pos = system.content.find("Available Skills").unwrap();
let runtime_pos = system.content.find("Runtime Context").unwrap();
assert!(skills_pos < runtime_pos);
}
#[test]
fn test_context_builder_runtime_context_in_messages() {
let ctx = RuntimeContext::new()
.with_channel("telegram")
.with_os_info();
let builder = ContextBuilder::new().with_runtime_context(ctx);
let messages = builder.build_messages(&[], "Hello");
assert_eq!(messages.len(), 2);
assert!(messages[0].content.contains("Runtime Context"));
assert!(messages[0].content.contains("telegram"));
}
#[test]
fn test_context_builder_with_memory_context() {
let builder = ContextBuilder::new()
.with_memory_context("## Memory\n\n### Pinned\n- user:name: Alice".to_string());
let system = builder.build_system_message();
assert!(system.content.contains("## Memory"));
assert!(system.content.contains("user:name: Alice"));
}
#[test]
fn test_context_builder_empty_memory_context_skipped() {
let builder = ContextBuilder::new().with_memory_context(String::new());
let system = builder.build_system_message();
assert!(!system.content.contains("## Memory"));
}
#[test]
fn test_context_builder_memory_after_runtime() {
let ctx = RuntimeContext::new().with_channel("cli");
let builder = ContextBuilder::new()
.with_runtime_context(ctx)
.with_memory_context("## Memory\n\n### Pinned\n- k: v".to_string());
let system = builder.build_system_message();
let runtime_pos = system.content.find("Runtime Context").unwrap();
let memory_pos = system.content.find("## Memory").unwrap();
assert!(runtime_pos < memory_pos);
}
#[test]
fn test_build_messages_with_memory_override_some() {
let builder = ContextBuilder::new()
.with_memory_context("## Memory\n\n### Pinned\n- old: data".to_string());
let override_ctx =
"## Memory\n\n### Pinned\n- user:name: Alice\n\n### Relevant\n- fact:project: ZeptoClaw";
let messages =
builder.build_messages_with_memory_override(&[], "Hello", Some(override_ctx));
assert!(messages[0].content.contains("fact:project: ZeptoClaw"));
assert!(!messages[0].content.contains("old: data"));
assert_eq!(messages.len(), 2);
}
#[test]
fn test_build_messages_with_memory_override_none_falls_back() {
let builder = ContextBuilder::new()
.with_memory_context("## Memory\n\n### Pinned\n- stored: value".to_string());
let messages = builder.build_messages_with_memory_override(&[], "Hello", None);
assert!(messages[0].content.contains("stored: value"));
}
#[test]
fn test_build_messages_with_memory_override_empty_string() {
let builder = ContextBuilder::new()
.with_memory_context("## Memory\n\n### Pinned\n- old: data".to_string());
let messages = builder.build_messages_with_memory_override(&[], "Hello", Some(""));
assert!(!messages[0].content.contains("## Memory"));
}
#[test]
fn test_system_prompt_mentions_memory() {
assert!(
DEFAULT_SYSTEM_PROMPT.contains("longterm_memory"),
"DEFAULT_SYSTEM_PROMPT should mention longterm_memory tool"
);
}
#[test]
fn test_system_prompt_mentions_save_and_recall() {
assert!(
DEFAULT_SYSTEM_PROMPT.contains("Save") || DEFAULT_SYSTEM_PROMPT.contains("save"),
"DEFAULT_SYSTEM_PROMPT should mention saving facts"
);
assert!(
DEFAULT_SYSTEM_PROMPT.contains("Recall") || DEFAULT_SYSTEM_PROMPT.contains("recall"),
"DEFAULT_SYSTEM_PROMPT should mention recalling information"
);
}
#[test]
fn test_system_prompt_memory_instructions_in_built_message() {
let builder = ContextBuilder::new();
let system = builder.build_system_message();
assert!(
system.content.contains("longterm_memory"),
"Built system message should contain longterm_memory instructions"
);
}
#[test]
fn test_system_prompt_contains_reminder_guidance() {
assert!(
DEFAULT_SYSTEM_PROMPT.contains("Reminder:"),
"System prompt must instruct how to handle cron-delivered 'Reminder:' messages"
);
}
#[test]
fn test_system_prompt_contains_heartbeat_guidance() {
assert!(
DEFAULT_SYSTEM_PROMPT.contains("HEARTBEAT_OK"),
"System prompt must instruct how to handle heartbeat messages"
);
}
#[test]
fn test_with_system_prompt_suffix() {
let builder = ContextBuilder::new().with_system_prompt_suffix("\n\nExtra instructions.");
let system = builder.build_system_message();
assert!(system.content.contains("ZeptoClaw"));
assert!(system.content.contains("Extra instructions."));
}
#[test]
fn test_first_run_persona_prompt_content() {
assert!(FIRST_RUN_PERSONA_PROMPT.contains("First Conversation Setup"));
assert!(FIRST_RUN_PERSONA_PROMPT.contains("concise"));
assert!(FIRST_RUN_PERSONA_PROMPT.contains("persona_pref"));
}
}