Skip to main content

netget/client/mssql/
actions.rs

1//! MSSQL client protocol actions implementation
2
3use crate::llm::actions::{
4    client_trait::{Client, ClientActionResult},
5    protocol_trait::Protocol,
6    ActionDefinition, Parameter,
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/// MSSQL client connected event
15pub static MSSQL_CLIENT_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
16    EventType::new(
17        "mssql_connected",
18        "MSSQL client successfully connected to server",
19        json!({
20            "type": "execute_query",
21            "query": "SELECT @@VERSION"
22        }),
23    )
24    .with_parameters(vec![Parameter {
25        name: "remote_addr".to_string(),
26        type_hint: "string".to_string(),
27        description: "MSSQL server address".to_string(),
28        required: true,
29    }])
30});
31
32/// MSSQL client query result received event
33pub static MSSQL_CLIENT_QUERY_RESULT_EVENT: LazyLock<EventType> = LazyLock::new(|| {
34    EventType::new(
35        "mssql_query_result",
36        "Query result received from MSSQL server",
37        json!({
38            "type": "wait_for_more"
39        }),
40    )
41    .with_parameters(vec![
42        Parameter {
43            name: "columns".to_string(),
44            type_hint: "array".to_string(),
45            description: "Column definitions from result set".to_string(),
46            required: true,
47        },
48        Parameter {
49            name: "rows".to_string(),
50            type_hint: "array".to_string(),
51            description: "Rows from result set".to_string(),
52            required: true,
53        },
54        Parameter {
55            name: "rows_affected".to_string(),
56            type_hint: "number".to_string(),
57            description: "Number of rows affected (for non-SELECT queries)".to_string(),
58            required: false,
59        },
60    ])
61});
62
63/// MSSQL client error event
64pub static MSSQL_CLIENT_ERROR_EVENT: LazyLock<EventType> = LazyLock::new(|| {
65    EventType::new(
66        "mssql_error",
67        "Error received from MSSQL server",
68        json!({"type": "placeholder", "event_id": "mssql_error"}),
69    )
70    .with_parameters(vec![
71        Parameter {
72            name: "error_number".to_string(),
73            type_hint: "number".to_string(),
74            description: "MSSQL error number".to_string(),
75            required: true,
76        },
77        Parameter {
78            name: "message".to_string(),
79            type_hint: "string".to_string(),
80            description: "Error message".to_string(),
81            required: true,
82        },
83    ])
84});
85
86/// MSSQL client protocol action handler
87pub struct MssqlClientProtocol;
88
89impl MssqlClientProtocol {
90    pub fn new() -> Self {
91        Self
92    }
93}
94
95// Implement Protocol trait (common functionality)
96impl Protocol for MssqlClientProtocol {
97    fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
98        vec![
99            ActionDefinition {
100                name: "execute_query".to_string(),
101                description: "Execute an SQL query on the MSSQL server".to_string(),
102                parameters: vec![Parameter {
103                    name: "query".to_string(),
104                    type_hint: "string".to_string(),
105                    description: "SQL query to execute (e.g., SELECT * FROM users)".to_string(),
106                    required: true,
107                }],
108                example: json!({
109                    "type": "execute_query",
110                    "query": "SELECT @@VERSION"
111                }),
112                log_template: None,
113            },
114            ActionDefinition {
115                name: "disconnect".to_string(),
116                description: "Disconnect from the MSSQL 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![
127            ActionDefinition {
128                name: "execute_query".to_string(),
129                description: "Execute an SQL query in response to received data".to_string(),
130                parameters: vec![Parameter {
131                    name: "query".to_string(),
132                    type_hint: "string".to_string(),
133                    description: "SQL query".to_string(),
134                    required: true,
135                }],
136                example: json!({
137                    "type": "execute_query",
138                    "query": "INSERT INTO log VALUES ('event')"
139                }),
140                log_template: None,
141            },
142            ActionDefinition {
143                name: "wait_for_more".to_string(),
144                description: "Wait for more data without taking action".to_string(),
145                parameters: vec![],
146                example: json!({
147                    "type": "wait_for_more"
148                }),
149                log_template: None,
150            },
151        ]
152    }
153    fn protocol_name(&self) -> &'static str {
154        "MSSQL"
155    }
156    fn get_event_types(&self) -> Vec<EventType> {
157        vec![
158            EventType::new(
159                "mssql_connected",
160                "Triggered when MSSQL client connects to server",
161                json!({"type": "placeholder", "event_id": "mssql_connected"}),
162            ),
163            EventType::new(
164                "mssql_query_result",
165                "Triggered when MSSQL client receives query result",
166                json!({"type": "placeholder", "event_id": "mssql_query_result"}),
167            ),
168            EventType::new(
169                "mssql_error",
170                "Triggered when MSSQL client receives error",
171                json!({"type": "placeholder", "event_id": "mssql_error"}),
172            ),
173        ]
174    }
175    fn stack_name(&self) -> &'static str {
176        "ETH>IP>TCP>TDS>MSSQL"
177    }
178    fn keywords(&self) -> Vec<&'static str> {
179        vec![
180            "mssql",
181            "mssql client",
182            "sql server client",
183            "connect to mssql",
184        ]
185    }
186    fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
187        use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
188
189        ProtocolMetadataV2::builder()
190            .state(DevelopmentState::Experimental)
191            .implementation("tiberius TDS client library v0.12")
192            .llm_control("Full control over SQL queries")
193            .e2e_testing("Local MSSQL server or Docker container")
194            .build()
195    }
196    fn description(&self) -> &'static str {
197        "MSSQL client for SQL Server database operations"
198    }
199    fn example_prompt(&self) -> &'static str {
200        "Connect to MSSQL at localhost:1433 and execute SELECT @@VERSION"
201    }
202    fn group_name(&self) -> &'static str {
203        "Database"
204    }
205    fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
206        use crate::llm::actions::StartupExamples;
207        use serde_json::json;
208
209        StartupExamples::new(
210            // LLM mode: LLM controls MSSQL queries
211            json!({
212                "type": "open_client",
213                "remote_addr": "localhost:1433;database=master;user=sa",
214                "base_stack": "mssql",
215                "instruction": "Query the database version and list all tables"
216            }),
217            // Script mode: Code-based SQL execution
218            json!({
219                "type": "open_client",
220                "remote_addr": "localhost:1433;database=master;user=sa",
221                "base_stack": "mssql",
222                "event_handlers": [{
223                    "event_pattern": "mssql_query_result",
224                    "handler": {
225                        "type": "script",
226                        "language": "python",
227                        "code": "<mssql_client_handler>"
228                    }
229                }]
230            }),
231            // Static mode: Fixed query responses
232            json!({
233                "type": "open_client",
234                "remote_addr": "localhost:1433;database=master;user=sa",
235                "base_stack": "mssql",
236                "event_handlers": [
237                    {
238                        "event_pattern": "mssql_connected",
239                        "handler": {
240                            "type": "static",
241                            "actions": [{
242                                "type": "execute_query",
243                                "query": "SELECT @@VERSION"
244                            }]
245                        }
246                    },
247                    {
248                        "event_pattern": "mssql_query_result",
249                        "handler": {
250                            "type": "static",
251                            "actions": [{
252                                "type": "disconnect"
253                            }]
254                        }
255                    }
256                ]
257            }),
258        )
259    }
260}
261
262// Implement Client trait (client-specific functionality)
263impl Client for MssqlClientProtocol {
264    fn connect(
265        &self,
266        ctx: crate::protocol::ConnectContext,
267    ) -> std::pin::Pin<
268        Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
269    > {
270        Box::pin(async move {
271            use crate::client::mssql::MssqlClient;
272            MssqlClient::connect_with_llm_actions(
273                ctx.remote_addr,
274                ctx.llm_client,
275                ctx.state,
276                ctx.status_tx,
277                ctx.client_id,
278            )
279            .await
280        })
281    }
282    fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
283        let action_type = action
284            .get("type")
285            .and_then(|v| v.as_str())
286            .context("Missing 'type' field in action")?;
287
288        match action_type {
289            "execute_query" => {
290                let query = action
291                    .get("query")
292                    .and_then(|v| v.as_str())
293                    .context("Missing 'query' field")?
294                    .to_string();
295
296                Ok(ClientActionResult::Custom {
297                    name: "mssql_query".to_string(),
298                    data: json!({
299                        "query": query,
300                    }),
301                })
302            }
303            "disconnect" => Ok(ClientActionResult::Disconnect),
304            "wait_for_more" => Ok(ClientActionResult::WaitForMore),
305            _ => Err(anyhow::anyhow!(
306                "Unknown MSSQL client action: {}",
307                action_type
308            )),
309        }
310    }
311}