netget/client/dc_peer/
actions.rs1use 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 DcPeerClientProtocol;
15impl DcPeerClientProtocol {
16 pub fn new() -> Self {
17 Self
18 }
19}
20pub fn requests() -> Vec<ActionDefinition> {
21 vec![
22 action(
23 "dc_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":"dc_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 DcPeerClientProtocol {
62 fn protocol_name(&self) -> &'static str {
63 "NMDC Peer"
64 }
65 fn stack_name(&self) -> &'static str {
66 "ETH>IP>TCP>NMDC Peer"
67 }
68 fn keywords(&self) -> Vec<&'static str> {
69 vec!["dc_peer", "nmdc peer"]
70 }
71 fn description(&self) -> &'static str {
72 "Selected NMDC Peer file transfers connecting role"
73 }
74 fn example_prompt(&self) -> &'static str {
75 "Connect to NMDC Peer on 127.0.0.1:412"
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 let mut fields = crate::server::p2p_support::tls_parameters(false);
82 fields.push(crate::server::dc_peer::codec::nickname_parameter(
83 crate::server::dc_peer::codec::DEFAULT_CONNECTOR_NICKNAME,
84 ));
85 fields
86 }
87 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
88 let base = json!({"type":"open_client","protocol":"dc_peer","remote_addr":"127.0.0.1:412","instruction":"Use selected NMDC Peer operations"});
89 let mut fixed = base.clone();
90 fixed["event_handlers"] = json!([{"event_pattern":"dc_peer_connected","handler":{"type":"static","actions":[requests()[0].example]}}]);
91 let mut script = base.clone();
92 script["event_handlers"] = json!([{"event_pattern":"dc_peer_connected","handler":{"type":"script","language":"python","code":"import json\nprint(json.dumps({'actions':[{'type':'disconnect'}]}))"}}]);
93 crate::llm::actions::StartupExamples::new(base, script, fixed)
94 }
95 fn get_async_actions(&self, _: &AppState) -> Vec<ActionDefinition> {
96 requests()
97 }
98 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
99 vec![]
100 }
101 fn get_event_types(&self) -> Vec<EventType> {
102 EVENTS.clone()
103 }
104 fn metadata(&self) -> crate::protocol::ProtocolMetadataV2 {
105 crate::protocol::ProtocolMetadataV2::builder().state(crate::protocol::metadata::DevelopmentState::Experimental).implementation("Native bounded NMDC 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("NMDC uploader/listener and downloader/connector. ADCGET/ADCSND file requests, XML/BZip2 file lists and optional full-payload Tiger tree hash verification. Configurable local nickname must match hub rendezvous identity. 1 MiB per transfer; no filesystem access, multi-source scheduler, peer discovery or push uploads. 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::dc_peer::codec::MAX_COMMAND).well_known_port(412).build()
106 }
107}
108impl Client for DcPeerClientProtocol {
109 fn connect(
110 &self,
111 ctx: ConnectContext,
112 ) -> Pin<Box<dyn Future<Output = Result<SocketAddr>> + Send>> {
113 Box::pin(super::connect(ctx))
114 }
115 fn execute_action(&self, v: Value) -> Result<ClientActionResult> {
116 if v["type"] == "disconnect" {
117 return Ok(ClientActionResult::Disconnect);
118 }
119 crate::server::dc_peer::codec::validate(&v)?;
120 Ok(ClientActionResult::Custom {
121 name: v["type"].as_str().expect("validated type").into(),
122 data: v,
123 })
124 }
125}
126pub static EVENTS: LazyLock<Vec<EventType>> = LazyLock::new(|| {
127 vec![
128 EventType::new(
129 "dc_peer_connected",
130 "Peer negotiation completed",
131 json!({"type":"disconnect"}),
132 )
133 .with_actions(requests()),
134 EventType::new(
135 "dc_peer_response",
136 "Protocol response or incoming message",
137 json!({"type":"disconnect"}),
138 )
139 .with_actions(requests()),
140 ]
141});