1use crate::llm::actions::{
2 client_trait::{Client, ClientActionResult},
3 protocol_trait::Protocol,
4 ActionDefinition,
5};
6use crate::protocol::{ConnectContext, EventType};
7use crate::server::p2p_support::{action, parameter};
8use crate::state::AppState;
9use anyhow::Result;
10use serde_json::{json, Value};
11use std::{future::Future, net::SocketAddr, pin::Pin, sync::LazyLock};
12
13#[derive(Default)]
14pub struct AdcPeerClientProtocol;
15impl AdcPeerClientProtocol {
16 pub fn new() -> Self {
17 Self
18 }
19}
20pub fn requests() -> Vec<ActionDefinition> {
21 vec![
22 action(
23 "adc_peer_get",
24 "Download a file or file list",
25 vec![
26 parameter(
27 "identifier",
28 "string",
29 "TTH/hash, files.xml.bz2 or relative file identifier",
30 true,
31 ),
32 parameter(
33 "offset",
34 "number",
35 "Starting byte offset within the requested file (zero based)",
36 true,
37 ),
38 parameter(
39 "length",
40 "number",
41 "Bytes to transfer (0..1048576) or -1 for remainder",
42 true,
43 ),
44 parameter(
45 "expected_tth",
46 "string",
47 "Optional whole-payload Tiger tree hash",
48 false,
49 ),
50 ],
51 json!({"type":"adc_peer_get","identifier":"files.xml.bz2","offset":0,"length":-1}),
52 ),
53 action(
54 "disconnect",
55 "Close this peer",
56 vec![],
57 json!({"type":"disconnect"}),
58 ),
59 ]
60}
61impl Protocol for AdcPeerClientProtocol {
62 fn protocol_name(&self) -> &'static str {
63 "ADC Peer"
64 }
65 fn stack_name(&self) -> &'static str {
66 "ETH>IP>TCP>ADC Peer"
67 }
68 fn keywords(&self) -> Vec<&'static str> {
69 vec!["adc_peer", "adc peer"]
70 }
71 fn description(&self) -> &'static str {
72 "Selected ADC Peer file transfers connecting role"
73 }
74 fn example_prompt(&self) -> &'static str {
75 "Connect to ADC Peer on 127.0.0.1:1512"
76 }
77 fn group_name(&self) -> &'static str {
78 "Peer-to-peer"
79 }
80 fn get_startup_parameters(&self) -> Vec<crate::llm::actions::ParameterDefinition> {
81 {
82 let mut p = crate::server::p2p_support::tls_parameters(false);
83 for (name, description) in [
84 (
85 "cid",
86 "Own ADC CID matching the identity announced on the hub",
87 ),
88 ("token", "Rendezvous token from DCTM/DRCM"),
89 ] {
90 p.push(crate::llm::actions::ParameterDefinition {
91 name: name.into(),
92 description: description.into(),
93 type_hint: "string".into(),
94 required: false,
95 example: json!(""),
96 default: None,
97 });
98 }
99 p
100 }
101 }
102 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
103 let base = json!({"type":"open_client","protocol":"adc_peer","remote_addr":"127.0.0.1:1512","instruction":"Use selected ADC Peer operations"});
104 let mut fixed = base.clone();
105 fixed["event_handlers"] = json!([{"event_pattern":"adc_peer_connected","handler":{"type":"static","actions":[requests()[0].example]}}]);
106 let mut script = base.clone();
107 script["event_handlers"] = json!([{"event_pattern":"adc_peer_connected","handler":{"type":"script","language":"python","code":"import json\nprint(json.dumps({'actions':[{'type':'disconnect'}]}))"}}]);
108 crate::llm::actions::StartupExamples::new(base, script, fixed)
109 }
110 fn get_async_actions(&self, _: &AppState) -> Vec<ActionDefinition> {
111 requests()
112 }
113 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
114 vec![]
115 }
116 fn get_event_types(&self) -> Vec<EventType> {
117 EVENTS.clone()
118 }
119 fn metadata(&self) -> crate::protocol::ProtocolMetadataV2 {
120 crate::protocol::ProtocolMetadataV2::builder().state(crate::protocol::metadata::DevelopmentState::Experimental).implementation("Native bounded ADC Peer selected codec; TCP and optional implicit TLS").llm_control("Handler-supplied file and file-list downloads, ranges and refusals").e2e_testing("Independent peer exchanges, malformed input, owner shutdown and standalone feature builds").notes("ADC BASE/TIGR CINF negotiation and selected CGET/CSND file ranges; ADCS uses verified implicit TLS. XML/BZip2 file lists and optional full-payload Tiger tree hash verification. 1 MiB per transfer. Direct connections only; no hub token binding, multi-source scheduler, NAT traversal or filesystem access. 256 connections, 30 s first frame, 600 s idle, 10 s handshake/exchange. No persistent protocol data; handlers supply content and decisions.").max_inbound_bytes(crate::server::adc_peer::codec::MAX_COMMAND).well_known_port(1512).build()
121 }
122}
123impl Client for AdcPeerClientProtocol {
124 fn connect(
125 &self,
126 ctx: ConnectContext,
127 ) -> Pin<Box<dyn Future<Output = Result<SocketAddr>> + Send>> {
128 Box::pin(super::connect(ctx))
129 }
130 fn execute_action(&self, v: Value) -> Result<ClientActionResult> {
131 if v["type"] == "disconnect" {
132 return Ok(ClientActionResult::Disconnect);
133 }
134 crate::server::adc_peer::codec::validate(&v)?;
135 Ok(ClientActionResult::Custom {
136 name: v["type"].as_str().expect("validated type").into(),
137 data: v,
138 })
139 }
140}
141pub static EVENTS: LazyLock<Vec<EventType>> = LazyLock::new(|| {
142 vec![
143 EventType::new(
144 "adc_peer_connected",
145 "Peer negotiation completed",
146 json!({"type":"disconnect"}),
147 )
148 .with_actions(requests()),
149 EventType::new(
150 "adc_peer_response",
151 "Protocol response or incoming message",
152 json!({"type":"disconnect"}),
153 )
154 .with_actions(requests()),
155 ]
156});