Skip to main content

stynx_code_tools/infrastructure/
synthetic_output_tool.rs

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