swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/crates/fidget-spinner-cli/src/mcp/output.rs
blob: 58f7eb401cfb0e94855b80f90f9d39c1ba8b2f7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use libmcp::{JsonPorcelainConfig, RenderMode, render_json_porcelain};
use serde::Serialize;
use serde_json::{Map, Value, json};

use crate::mcp::fault::{FaultKind, FaultRecord, FaultStage};

pub(crate) fn split_render_mode(
    arguments: Value,
    operation: &str,
    stage: FaultStage,
) -> Result<(RenderMode, Value), FaultRecord> {
    let Value::Object(mut object) = arguments else {
        return Ok((RenderMode::Porcelain, arguments));
    };
    let render = object
        .remove("render")
        .map(|value| {
            serde_json::from_value::<RenderMode>(value).map_err(|error| {
                FaultRecord::new(
                    FaultKind::InvalidInput,
                    stage,
                    operation,
                    format!("invalid render mode: {error}"),
                )
            })
        })
        .transpose()?
        .unwrap_or(RenderMode::Porcelain);
    Ok((render, Value::Object(object)))
}

pub(crate) fn tool_success(
    value: &impl Serialize,
    render: RenderMode,
    stage: FaultStage,
    operation: &str,
) -> Result<Value, FaultRecord> {
    let structured = serde_json::to_value(value).map_err(|error| {
        FaultRecord::new(FaultKind::Internal, stage, operation, error.to_string())
    })?;
    tool_success_from_value(structured, render, stage, operation)
}

pub(crate) fn tool_success_from_value(
    structured: Value,
    render: RenderMode,
    stage: FaultStage,
    operation: &str,
) -> Result<Value, FaultRecord> {
    let text = match render {
        RenderMode::Porcelain => render_json_porcelain(&structured, JsonPorcelainConfig::default()),
        RenderMode::Json => crate::to_pretty_json(&structured).map_err(|error| {
            FaultRecord::new(FaultKind::Internal, stage, operation, error.to_string())
        })?,
    };
    Ok(json!({
        "content": [{
            "type": "text",
            "text": text,
        }],
        "structuredContent": structured,
        "isError": false,
    }))
}

pub(crate) fn with_render_property(schema: Value) -> Value {
    let Value::Object(mut object) = schema else {
        return schema;
    };

    let properties = object
        .entry("properties".to_owned())
        .or_insert_with(|| Value::Object(Map::new()));
    if let Value::Object(properties) = properties {
        let _ = properties.insert(
            "render".to_owned(),
            json!({
                "type": "string",
                "enum": ["porcelain", "json"],
                "description": "Output mode. Defaults to porcelain for model-friendly summaries."
            }),
        );
    }
    let _ = object
        .entry("additionalProperties".to_owned())
        .or_insert(Value::Bool(false));
    Value::Object(object)
}