claude_rust_tools/infrastructure/
synthetic_output_tool.rs1use claude_rust_errors::AppResult;
2use claude_rust_types::{PermissionLevel, Tool};
3use serde_json::{Value, json};
4
5pub struct SyntheticOutputTool;
7
8impl SyntheticOutputTool {
9 pub fn new() -> Self {
10 Self
11 }
12}
13
14#[async_trait::async_trait]
15impl Tool for SyntheticOutputTool {
16 fn name(&self) -> &str {
17 "synthetic_output"
18 }
19
20 fn description(&self) -> &str {
21 "Return content as structured output in a specified format (text, json, or markdown)."
22 }
23
24 fn input_schema(&self) -> Value {
25 json!({
26 "type": "object",
27 "properties": {
28 "content": {
29 "type": "string",
30 "description": "The content to output"
31 },
32 "format": {
33 "type": "string",
34 "description": "Output format: \"text\", \"json\", or \"markdown\"",
35 "enum": ["text", "json", "markdown"]
36 }
37 },
38 "required": ["content"]
39 })
40 }
41
42 fn permission_level(&self) -> PermissionLevel {
43 PermissionLevel::ReadOnly
44 }
45
46 fn is_read_only(&self, _input: &Value) -> bool { true }
47 fn is_concurrent_safe(&self, _input: &Value) -> bool { true }
48
49 async fn execute(&self, input: Value) -> AppResult<String> {
50 let content = input
51 .get("content")
52 .and_then(|v| v.as_str())
53 .ok_or_else(|| claude_rust_errors::AppError::Tool("missing 'content' field".into()))?;
54
55 let format = input
56 .get("format")
57 .and_then(|v| v.as_str())
58 .unwrap_or("text");
59
60 tracing::info!(format, content_len = content.len(), "synthetic output");
61
62 match format {
63 "json" => {
64 if serde_json::from_str::<Value>(content).is_ok() {
66 Ok(content.to_string())
67 } else {
68 Ok(json!({ "content": content }).to_string())
69 }
70 }
71 "markdown" | "text" => Ok(content.to_string()),
72 other => Err(claude_rust_errors::AppError::Tool(
73 format!("unsupported format: {other}. Use 'text', 'json', or 'markdown'.")
74 )),
75 }
76 }
77}