Skip to main content

netget/client/xmlrpc/
actions.rs

1//! XML-RPC client protocol actions implementation
2
3use crate::llm::actions::{
4    client_trait::{Client, ClientActionResult},
5    protocol_trait::Protocol,
6    ActionDefinition, Parameter, ParameterDefinition,
7};
8use crate::protocol::EventType;
9use crate::state::app_state::AppState;
10use anyhow::{Context, Result};
11use serde_json::json;
12use std::sync::LazyLock;
13
14/// XML-RPC client connected event
15pub static XMLRPC_CLIENT_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
16    EventType::new(
17        "xmlrpc_connected",
18        "XML-RPC client initialized and ready to call methods",
19        json!({
20            "type": "call_xmlrpc_method",
21            "method_name": "system.listMethods",
22            "params": []
23        }),
24    )
25    .with_parameters(vec![Parameter {
26        name: "server_url".to_string(),
27        type_hint: "string".to_string(),
28        description: "XML-RPC server URL".to_string(),
29        required: true,
30    }])
31});
32
33/// XML-RPC client response received event
34pub static XMLRPC_CLIENT_RESPONSE_RECEIVED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
35    EventType::new(
36        "xmlrpc_response_received",
37        "XML-RPC response received from server",
38        json!({
39            "type": "call_xmlrpc_method",
40            "method_name": "system.listMethods",
41            "params": []
42        }),
43    )
44    .with_parameters(vec![
45        Parameter {
46            name: "method_name".to_string(),
47            type_hint: "string".to_string(),
48            description: "Name of the method that was called".to_string(),
49            required: true,
50        },
51        Parameter {
52            name: "result".to_string(),
53            type_hint: "any".to_string(),
54            description: "Method call result (can be string, number, array, struct, etc.)"
55                .to_string(),
56            required: false,
57        },
58        Parameter {
59            name: "fault".to_string(),
60            type_hint: "object".to_string(),
61            description: "Fault information if the call failed".to_string(),
62            required: false,
63        },
64    ])
65});
66
67/// XML-RPC client protocol action handler
68pub struct XmlRpcClientProtocol;
69
70impl XmlRpcClientProtocol {
71    pub fn new() -> Self {
72        Self
73    }
74}
75
76// Implement Protocol trait (common functionality)
77impl Protocol for XmlRpcClientProtocol {
78    fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
79        vec![ParameterDefinition {
80            name: "timeout_secs".to_string(),
81            description: "Request timeout in seconds (default: 30)".to_string(),
82            type_hint: "number".to_string(),
83            required: false,
84            example: json!(30),
85            default: None,
86        }]
87    }
88    fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
89        vec![
90                ActionDefinition {
91                    name: "call_xmlrpc_method".to_string(),
92                    description: "Call an XML-RPC method on the server".to_string(),
93                    parameters: vec![
94                        Parameter {
95                            name: "method_name".to_string(),
96                            type_hint: "string".to_string(),
97                            description: "Name of the XML-RPC method to call".to_string(),
98                            required: true,
99                        },
100                        Parameter {
101                            name: "params".to_string(),
102                            type_hint: "array".to_string(),
103                            description: "Array of parameters for the method call (strings, numbers, bools, arrays, objects)".to_string(),
104                            required: false,
105                        },
106                    ],
107                    example: json!({
108                        "type": "call_xmlrpc_method",
109                        "method_name": "examples.getStateName",
110                        "params": [41]
111                    }),
112                log_template: None,
113                },
114                ActionDefinition {
115                    name: "disconnect".to_string(),
116                    description: "Disconnect from the XML-RPC server".to_string(),
117                    parameters: vec![],
118                    example: json!({
119                        "type": "disconnect"
120                    }),
121                log_template: None,
122                },
123            ]
124    }
125    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
126        vec![ActionDefinition {
127            name: "call_xmlrpc_method".to_string(),
128            description: "Call another XML-RPC method in response to received data".to_string(),
129            parameters: vec![
130                Parameter {
131                    name: "method_name".to_string(),
132                    type_hint: "string".to_string(),
133                    description: "Name of the XML-RPC method to call".to_string(),
134                    required: true,
135                },
136                Parameter {
137                    name: "params".to_string(),
138                    type_hint: "array".to_string(),
139                    description: "Array of parameters for the method call".to_string(),
140                    required: false,
141                },
142            ],
143            example: json!({
144                "type": "call_xmlrpc_method",
145                "method_name": "system.listMethods",
146                "params": []
147            }),
148            log_template: None,
149        }]
150    }
151    fn protocol_name(&self) -> &'static str {
152        "XML-RPC"
153    }
154    fn get_event_types(&self) -> Vec<EventType> {
155        vec![
156            EventType::new(
157                "xmlrpc_connected",
158                "Triggered when XML-RPC client is initialized",
159                json!({"type": "placeholder", "event_id": "xmlrpc_connected"}),
160            ),
161            EventType::new(
162                "xmlrpc_response_received",
163                "Triggered when XML-RPC client receives a response",
164                json!({"type": "placeholder", "event_id": "xmlrpc_response_received"}),
165            ),
166        ]
167    }
168    fn stack_name(&self) -> &'static str {
169        "ETH>IP>TCP>HTTP>XML-RPC"
170    }
171    fn keywords(&self) -> Vec<&'static str> {
172        vec!["xmlrpc", "xml-rpc", "xml rpc", "rpc", "connect to xmlrpc"]
173    }
174    fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
175        use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
176
177        ProtocolMetadataV2::builder()
178            .state(DevelopmentState::Experimental)
179            .implementation("xmlrpc crate for method calls")
180            .llm_control("Full control over method calls with structured parameters")
181            .e2e_testing("Public XML-RPC test servers or local implementation")
182            .build()
183    }
184    fn description(&self) -> &'static str {
185        "XML-RPC client for calling remote procedures"
186    }
187    fn example_prompt(&self) -> &'static str {
188        "Connect to http://example.com/xmlrpc and call system.listMethods"
189    }
190    fn group_name(&self) -> &'static str {
191        "RPC & API"
192    }
193    fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
194        use crate::llm::actions::StartupExamples;
195        use serde_json::json;
196
197        StartupExamples::new(
198            // LLM mode: LLM controls XML-RPC method calls
199            json!({
200                "type": "open_client",
201                "remote_addr": "http://example.com/xmlrpc",
202                "base_stack": "xmlrpc",
203                "instruction": "Call system.listMethods to discover available methods"
204            }),
205            // Script mode: Code-based XML-RPC handling
206            json!({
207                "type": "open_client",
208                "remote_addr": "http://example.com/xmlrpc",
209                "base_stack": "xmlrpc",
210                "event_handlers": [{
211                    "event_pattern": "xmlrpc_response_received",
212                    "handler": {
213                        "type": "script",
214                        "language": "python",
215                        "code": "<xmlrpc_client_handler>"
216                    }
217                }]
218            }),
219            // Static mode: Fixed XML-RPC method call
220            json!({
221                "type": "open_client",
222                "remote_addr": "http://example.com/xmlrpc",
223                "base_stack": "xmlrpc",
224                "event_handlers": [
225                    {
226                        "event_pattern": "xmlrpc_connected",
227                        "handler": {
228                            "type": "static",
229                            "actions": [{
230                                "type": "call_xmlrpc_method",
231                                "method_name": "system.listMethods",
232                                "params": []
233                            }]
234                        }
235                    },
236                    {
237                        "event_pattern": "xmlrpc_response_received",
238                        "handler": {
239                            "type": "static",
240                            "actions": [{
241                                "type": "disconnect"
242                            }]
243                        }
244                    }
245                ]
246            }),
247        )
248    }
249}
250
251// Implement Client trait (client-specific functionality)
252impl Client for XmlRpcClientProtocol {
253    fn connect(
254        &self,
255        ctx: crate::protocol::ConnectContext,
256    ) -> std::pin::Pin<
257        Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
258    > {
259        Box::pin(async move {
260            use crate::client::xmlrpc::XmlRpcClient;
261            XmlRpcClient::connect_with_llm_actions(
262                ctx.remote_addr,
263                ctx.llm_client,
264                ctx.state,
265                ctx.status_tx,
266                ctx.client_id,
267                ctx.startup_params,
268            )
269            .await
270        })
271    }
272    fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
273        let action_type = action
274            .get("type")
275            .and_then(|v| v.as_str())
276            .context("Missing 'type' field in action")?;
277
278        match action_type {
279            "call_xmlrpc_method" => {
280                let method_name = action
281                    .get("method_name")
282                    .and_then(|v| v.as_str())
283                    .context("Missing 'method_name' field")?
284                    .to_string();
285
286                let params = action
287                    .get("params")
288                    .and_then(|v| v.as_array())
289                    .cloned()
290                    .unwrap_or_default();
291
292                // Return custom result with method call data
293                Ok(ClientActionResult::Custom {
294                    name: "xmlrpc_call".to_string(),
295                    data: json!({
296                        "method_name": method_name,
297                        "params": params,
298                    }),
299                })
300            }
301            "disconnect" => Ok(ClientActionResult::Disconnect),
302            _ => Err(anyhow::anyhow!(
303                "Unknown XML-RPC client action: {}",
304                action_type
305            )),
306        }
307    }
308}