netget/cli/
easy_startup.rs1use anyhow::{Context, Result};
4use serde_json::Value as JsonValue;
5use std::sync::Arc;
6use tracing::info;
7
8use crate::llm::OllamaClient;
9use crate::protocol::EASY_REGISTRY;
10use crate::state::{AppState, EasyId, EasyInstance, EasyStatus};
11
12pub async fn start_easy_protocol(
30 protocol_name: &str,
31 user_instruction: Option<String>,
32 port: Option<u16>,
33 state: Arc<AppState>,
34 llm_client: Arc<OllamaClient>,
35) -> Result<EasyId> {
36 let easy_protocol = EASY_REGISTRY
38 .get_by_name(protocol_name)
39 .ok_or_else(|| anyhow::anyhow!("Easy protocol '{}' not found", protocol_name))?;
40
41 let underlying_protocol = easy_protocol.underlying_protocol();
42
43 info!(
44 "Starting easy protocol '{}' (wrapping '{}')",
45 protocol_name, underlying_protocol
46 );
47
48 let easy_instance = EasyInstance::new(
50 EasyId::new(0), protocol_name.to_string(),
52 underlying_protocol.to_string(),
53 user_instruction.clone(),
54 );
55
56 let easy_id = state.add_easy_instance(easy_instance).await;
58
59 state
61 .update_easy_status(easy_id, EasyStatus::Starting)
62 .await;
63
64 let action = match easy_protocol.generate_startup_action(user_instruction.clone(), port) {
66 Ok(action) => action,
67 Err(error) => {
68 state
69 .update_easy_status(easy_id, EasyStatus::Error(error.to_string()))
70 .await;
71 return Err(error).context("Failed to generate startup action");
72 }
73 };
74
75 info!(
76 "Generated startup action: {}",
77 serde_json::to_string_pretty(&action)?
78 );
79
80 match execute_easy_startup_action(&action, &state, &llm_client).await {
82 Ok(underlying_id) => {
83 match underlying_id {
84 EasyUnderlyingId::Server(server_id) => {
85 state.link_server_to_easy(server_id, easy_id).await
86 }
87 EasyUnderlyingId::Client(client_id) => {
88 state.link_client_to_easy(client_id, easy_id).await
89 }
90 }
91
92 state.update_easy_status(easy_id, EasyStatus::Running).await;
94
95 Ok(easy_id)
96 }
97 Err(e) => {
98 state
100 .update_easy_status(easy_id, EasyStatus::Error(e.to_string()))
101 .await;
102 Err(e).context("Failed to start underlying protocol")
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum EasyUnderlyingId {
110 Server(crate::state::ServerId),
111 Client(crate::state::ClientId),
112}
113
114pub async fn execute_easy_startup_action(
117 action: &JsonValue,
118 state: &AppState,
119 llm_client: &OllamaClient,
120) -> Result<EasyUnderlyingId> {
121 let (status_tx, mut status_rx) = tokio::sync::mpsc::unbounded_channel();
122 tokio::spawn(async move {
124 while let Some(message) = status_rx.recv().await {
125 tracing::info!("{}", message);
126 }
127 });
128 match action.get("type").and_then(JsonValue::as_str) {
129 Some("open_server") => {
130 let form: crate::cli::management::ServerForm =
131 serde_json::from_value(action.clone())
132 .context("Invalid easy open_server action")?;
133 Ok(EasyUnderlyingId::Server(
134 form.create(state, status_tx).await?,
135 ))
136 }
137 Some("open_client") => {
138 let form: crate::cli::management::ClientForm =
139 serde_json::from_value(action.clone())
140 .context("Invalid easy open_client action")?;
141 Ok(EasyUnderlyingId::Client(
142 form.create(state, llm_client.clone(), status_tx).await?,
143 ))
144 }
145 Some(other) => anyhow::bail!("Unknown easy startup action type: {}", other),
146 None => anyhow::bail!("Startup action missing string 'type' field"),
147 }
148}