1use 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
14pub 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
32pub 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
63pub 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
86pub struct MssqlClientProtocol;
88
89impl MssqlClientProtocol {
90 pub fn new() -> Self {
91 Self
92 }
93}
94
95impl 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 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 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 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
262impl 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}