stynx_code_tools/infrastructure/
synthetic_output_tool.rs1use 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}