use cersei::prelude::*;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
struct WordCountTool;
#[async_trait]
impl Tool for WordCountTool {
fn name(&self) -> &str {
"word_count"
}
fn description(&self) -> &str {
"Count the number of words, lines, and characters in the given text."
}
fn permission_level(&self) -> PermissionLevel {
PermissionLevel::None
}
fn category(&self) -> ToolCategory {
ToolCategory::Custom
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The text to analyze"
}
},
"required": ["text"]
})
}
async fn execute(&self, input: serde_json::Value, _ctx: &ToolContext) -> ToolResult {
#[derive(Deserialize)]
struct Input {
text: String,
}
let input: Input = match serde_json::from_value(input) {
Ok(i) => i,
Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
};
let words = input.text.split_whitespace().count();
let lines = input.text.lines().count();
let chars = input.text.chars().count();
ToolResult::success(format!(
"Words: {}\nLines: {}\nCharacters: {}",
words, lines, chars
))
}
}
struct KvLookupTool {
store: Arc<parking_lot::Mutex<HashMap<String, String>>>,
}
#[async_trait]
impl Tool for KvLookupTool {
fn name(&self) -> &str {
"kv_lookup"
}
fn description(&self) -> &str {
"Look up a value by key from the key-value store. Returns the value or 'not found'."
}
fn permission_level(&self) -> PermissionLevel {
PermissionLevel::ReadOnly
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"key": { "type": "string", "description": "The key to look up" }
},
"required": ["key"]
})
}
async fn execute(&self, input: serde_json::Value, _ctx: &ToolContext) -> ToolResult {
#[derive(Deserialize)]
struct Input {
key: String,
}
let input: Input = match serde_json::from_value(input) {
Ok(i) => i,
Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
};
let store = self.store.lock();
match store.get(&input.key) {
Some(value) => ToolResult::success(format!("{} = {}", input.key, value)),
None => ToolResult::success(format!("Key '{}' not found", input.key)),
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let store = Arc::new(parking_lot::Mutex::new(HashMap::new()));
{
let mut s = store.lock();
s.insert("project".into(), "Cersei SDK".into());
s.insert("version".into(), "0.1.0".into());
s.insert("language".into(), "Rust".into());
s.insert("author".into(), "The Cersei Contributors".into());
}
let agent = Agent::builder()
.provider(Anthropic::from_env()?)
.tool(WordCountTool)
.tool(KvLookupTool {
store: store.clone(),
})
.tools(cersei::tools::filesystem()) .system_prompt("You are a helpful assistant. Use tools when needed.")
.max_turns(5)
.permission_policy(AllowAll)
.build()?;
let output = agent
.run("Look up the 'project' and 'version' keys, then count the words in the combined result.")
.await?;
println!("─── Result ───");
println!("{}", output.text());
println!("\nTool calls made:");
for tc in &output.tool_calls {
println!(
" {} → {} ({}ms)",
tc.name,
if tc.is_error { "ERR" } else { "OK" },
tc.duration.as_millis()
);
}
Ok(())
}