Skip to main content

netget/client/dc_peer/
actions.rs

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 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});