#![expect(
clippy::unused_async,
reason = "the generated handler awaits the call, and a plain value has nothing to await"
)]
#![expect(
clippy::unused_async_trait_impl,
reason = "the generated handler awaits the call, and a plain value has nothing to await"
)]
use protocol_droid::application::server::context::RequestContext;
use protocol_droid::domain::ir::{
Content, JsonObject, JsonObjectError, PromptMessage, ResourceContents, Role, TextContent,
};
use protocol_droid::domain::registry::{Primitive, PrimitiveKind, Prompt, Resource};
use protocol_droid::serde::Serialize;
use protocol_droid::serde_json;
#[derive(Serialize)]
struct Shout {
text: String,
length: usize,
}
struct Scratchpad;
#[protocol_droid::server(name = "scratchpad")]
impl Scratchpad {
#[protocol_droid::tool]
async fn shout(text: String) -> Result<Shout, String> {
let length = text.chars().count();
Ok(Shout {
text: text.to_uppercase(),
length,
})
}
#[protocol_droid::tool]
async fn negotiated(ctx: RequestContext) -> String {
ctx.peer().negotiated().to_string()
}
#[protocol_droid::resource(reader = read_readme)]
fn readme() -> Resource {
Resource {
description: Some("How the scratchpad is meant to be used.".to_string()),
mime_type: Some("text/markdown".to_string()),
..Resource::new("file:///scratchpad/README.md", "readme")
}
}
#[protocol_droid::prompt(getter = get_digest)]
fn digest() -> Prompt {
let mut prompt = Prompt::new("digest");
prompt.description = Some("Summarise the notes written since a date.".to_string());
prompt.with_argument("since", true)
}
fn read_readme(_context: RequestContext, uri: &str) -> Vec<ResourceContents> {
vec![
ResourceContents::text(uri, "# Scratchpad\n\nNotes go in `notes/`.\n")
.with_mime_type("text/markdown"),
]
}
fn get_digest(_context: RequestContext, arguments: &JsonObject) -> Vec<PromptMessage> {
let parsed: serde_json::Value = match serde_json::from_str(arguments.as_str()) {
Ok(parsed) => parsed,
Err(_) => return Vec::new(),
};
let since = parsed
.get("since")
.and_then(serde_json::Value::as_str)
.unwrap_or("yesterday");
vec![PromptMessage::new(
Role::User,
Content::Text(TextContent::new(format!(
"Summarise the notes written since {since}."
))),
)]
}
}
fn main() -> Result<(), JsonObjectError> {
let server = Scratchpad.into_server()?;
println!("server: {}", server.name());
println!("tools: {}", server.tool_count());
let snapshot = server.registry().snapshot();
for kind in PrimitiveKind::ALL {
for entry in snapshot.list(kind) {
println!("\n {}: {}", kind.as_str(), entry.id());
if let Primitive::Tool(tool) = entry.primitive() {
if let Some(description) = &tool.description {
println!(" description: {description}");
}
println!(" inputSchema: {}", tool.input_schema.as_str());
}
}
}
Ok(())
}