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 TCP_CLIENT_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
16 EventType::new(
17 "tcp_connected",
18 "TCP client successfully connected to server",
19 serde_json::json!({
20 "type": "send_tcp_data",
21 "data": "48656c6c6f"
22 }),
23 )
24 .with_parameters(vec![Parameter {
25 name: "remote_addr".to_string(),
26 type_hint: "string".to_string(),
27 description: "Remote server address".to_string(),
28 required: true,
29 }])
30});
31
32pub static TCP_CLIENT_DATA_RECEIVED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
34 EventType::new(
35 "tcp_data_received",
36 "Data received from TCP server",
37 json!({"type": "placeholder", "event_id": "tcp_data_received"}),
38 )
39 .with_parameters(vec![
40 Parameter {
41 name: "data_hex".to_string(),
42 type_hint: "string".to_string(),
43 description: "The data received (as hex string)".to_string(),
44 required: true,
45 },
46 Parameter {
47 name: "data_length".to_string(),
48 type_hint: "number".to_string(),
49 description: "Length of data in bytes".to_string(),
50 required: true,
51 },
52 ])
53});
54
55pub struct TcpClientProtocol;
57
58impl Default for TcpClientProtocol {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl TcpClientProtocol {
65 pub fn new() -> Self {
66 Self
67 }
68}
69
70impl Protocol for TcpClientProtocol {
72 fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
73 vec![
74 ActionDefinition {
75 name: "send_tcp_data".to_string(),
76 description: "Send raw TCP data to the server (UTF-8 string or hex-encoded)"
77 .to_string(),
78 parameters: vec![
79 Parameter {
80 name: "data".to_string(),
81 type_hint: "string".to_string(),
82 description: "UTF-8 string data to send (preferred for text)".to_string(),
83 required: false,
84 },
85 Parameter {
86 name: "data_hex".to_string(),
87 type_hint: "string".to_string(),
88 description: "Hexadecimal encoded data to send (for binary data)"
89 .to_string(),
90 required: false,
91 },
92 ],
93 example: json!({
94 "type": "send_tcp_data",
95 "data": "Hello World"
96 }),
97 log_template: None,
98 },
99 ActionDefinition {
100 name: "disconnect".to_string(),
101 description: "Disconnect from the TCP server".to_string(),
102 parameters: vec![],
103 example: json!({
104 "type": "disconnect"
105 }),
106 log_template: None,
107 },
108 ]
109 }
110 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
111 vec![
112 ActionDefinition {
113 name: "send_tcp_data".to_string(),
114 description:
115 "Send TCP data in response to received data (UTF-8 string or hex-encoded)"
116 .to_string(),
117 parameters: vec![
118 Parameter {
119 name: "data".to_string(),
120 type_hint: "string".to_string(),
121 description: "UTF-8 string data to send (preferred for text)".to_string(),
122 required: false,
123 },
124 Parameter {
125 name: "data_hex".to_string(),
126 type_hint: "string".to_string(),
127 description: "Hexadecimal encoded data to send (for binary data)"
128 .to_string(),
129 required: false,
130 },
131 ],
132 example: json!({
133 "type": "send_tcp_data",
134 "data": "Hello World"
135 }),
136 log_template: None,
137 },
138 ActionDefinition {
139 name: "wait_for_more".to_string(),
140 description: "Wait for more data before responding".to_string(),
141 parameters: vec![],
142 example: json!({
143 "type": "wait_for_more"
144 }),
145 log_template: None,
146 },
147 ]
148 }
149 fn protocol_name(&self) -> &'static str {
150 "TCP"
151 }
152 fn get_event_types(&self) -> Vec<EventType> {
153 vec![
154 EventType::new(
155 "tcp_connected",
156 "Triggered when TCP client connects to server",
157 json!({"type": "placeholder", "event_id": "tcp_connected"}),
158 ),
159 EventType::new(
160 "tcp_data_received",
161 "Triggered when TCP client receives data from server",
162 json!({"type": "placeholder", "event_id": "tcp_data_received"}),
163 ),
164 ]
165 }
166 fn stack_name(&self) -> &'static str {
167 "ETH>IP>TCP"
168 }
169 fn keywords(&self) -> Vec<&'static str> {
170 vec!["tcp", "tcp client", "connect to tcp"]
171 }
172 fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
173 use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
174
175 ProtocolMetadataV2::builder()
176 .state(DevelopmentState::Experimental)
177 .implementation("Tokio TcpStream for raw TCP connections")
178 .llm_control("Full control over sent/received bytes")
179 .e2e_testing("nc (netcat) as test server")
180 .build()
181 }
182 fn description(&self) -> &'static str {
183 "TCP client for connecting to TCP servers"
184 }
185 fn example_prompt(&self) -> &'static str {
186 "Connect to TCP at localhost:8080 and send 'HELLO'"
187 }
188 fn group_name(&self) -> &'static str {
189 "Core"
190 }
191 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
192 use crate::llm::actions::StartupExamples;
193 use serde_json::json;
194
195 StartupExamples::new(
196 json!({
198 "type": "open_client",
199 "remote_addr": "example.com:9000",
200 "base_stack": "tcp",
201 "instruction": "Connect to the TCP server and send a greeting, then echo any responses"
202 }),
203 json!({
205 "type": "open_client",
206 "remote_addr": "example.com:9000",
207 "base_stack": "tcp",
208 "event_handlers": [{
209 "event_pattern": "tcp_data_received",
210 "handler": {
211 "type": "script",
212 "language": "python",
213 "code": "<tcp_client_handler>"
214 }
215 }]
216 }),
217 json!({
219 "type": "open_client",
220 "remote_addr": "example.com:9000",
221 "base_stack": "tcp",
222 "event_handlers": [
223 {
224 "event_pattern": "tcp_connected",
225 "handler": {
226 "type": "static",
227 "actions": [{
228 "type": "send_tcp_data",
229 "data": "HELLO\r\n"
230 }]
231 }
232 },
233 {
234 "event_pattern": "tcp_data_received",
235 "handler": {
236 "type": "static",
237 "actions": [{
238 "type": "disconnect"
239 }]
240 }
241 }
242 ]
243 }),
244 )
245 }
246}
247
248impl Client for TcpClientProtocol {
250 fn connect(
251 &self,
252 ctx: crate::protocol::ConnectContext,
253 ) -> std::pin::Pin<
254 Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
255 > {
256 Box::pin(async move {
257 use crate::client::tcp::TcpClient;
258 TcpClient::connect_with_llm_actions(
259 ctx.remote_addr,
260 ctx.llm_client,
261 ctx.state,
262 ctx.status_tx,
263 ctx.client_id,
264 )
265 .await
266 })
267 }
268 fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
269 let action_type = action
270 .get("type")
271 .and_then(|v| v.as_str())
272 .context("Missing 'type' field in action")?;
273
274 match action_type {
275 "send_tcp_data" => {
276 let data = if let Some(utf8_data) = action.get("data").and_then(|v| v.as_str()) {
279 utf8_data.as_bytes().to_vec()
281 } else if let Some(hex_data) = action.get("data_hex").and_then(|v| v.as_str()) {
282 hex::decode(hex_data).context("Invalid hex data in data_hex field")?
284 } else {
285 return Err(anyhow::anyhow!(
286 "Missing 'data' or 'data_hex' field in send_tcp_data action"
287 ));
288 };
289
290 Ok(ClientActionResult::SendData(data))
291 }
292 "disconnect" => Ok(ClientActionResult::Disconnect),
293 "wait_for_more" => Ok(ClientActionResult::WaitForMore),
294 _ => Err(anyhow::anyhow!(
295 "Unknown TCP client action: {}",
296 action_type
297 )),
298 }
299 }
300}