mesh_llm_api_client/
client.rs1use crate::events::{Event, EventListener};
2use crate::{InviteToken, OwnerKeypair};
3use mesh_client::ClientError;
4use std::sync::Arc;
5use std::time::Duration;
6use thiserror::Error;
7
8pub const MAX_RECONNECT_ATTEMPTS: u32 = mesh_client::client::builder::MAX_RECONNECT_ATTEMPTS;
9
10#[derive(Debug, Error)]
11pub enum MeshApiError {
12 #[error(transparent)]
13 Client(#[from] ClientError),
14 #[error("public mesh discovery failed: {message}")]
15 Discovery { message: String },
16 #[error("no public mesh matched the requested criteria")]
17 NoPublicMeshFound,
18 #[error("invalid invite token: {message}")]
19 InvalidInviteToken { message: String },
20 #[error("invalid Mesh SDK configuration: {message}")]
21 InvalidConfig { message: &'static str },
22 #[error("model management failed: {message}")]
23 ModelManagement { message: String },
24 #[error("serving failed: {message}")]
25 Serving { message: String },
26 #[error("{feature} is not implemented in the Mesh SDK yet")]
27 Unsupported { feature: &'static str },
28}
29
30#[derive(Clone, Debug)]
31pub struct ClientConfig {
32 pub owner_keypair: OwnerKeypair,
33 pub invite_token: InviteToken,
34 pub user_agent: String,
35 pub connect_timeout: Duration,
36}
37
38pub struct ClientBuilder {
39 config: ClientConfig,
40}
41
42impl ClientBuilder {
43 pub fn new(owner_keypair: OwnerKeypair, invite_token: InviteToken) -> Self {
44 Self {
45 config: ClientConfig {
46 owner_keypair,
47 invite_token,
48 user_agent: format!("mesh-llm-api-client/{}", env!("CARGO_PKG_VERSION")),
49 connect_timeout: Duration::from_secs(30),
50 },
51 }
52 }
53
54 pub fn with_user_agent(mut self, ua: String) -> Self {
55 self.config.user_agent = ua;
56 self
57 }
58
59 pub fn with_connect_timeout(mut self, d: Duration) -> Self {
60 self.config.connect_timeout = d;
61 self
62 }
63
64 pub fn build(self) -> Result<MeshClient, MeshApiError> {
65 let inner = mesh_client::ClientBuilder::new(
66 self.config.owner_keypair.into_inner(),
67 self.config.invite_token.into_inner(),
68 )
69 .with_user_agent(self.config.user_agent.clone())
70 .with_connect_timeout(self.config.connect_timeout)
71 .build()?;
72
73 Ok(MeshClient { inner })
74 }
75}
76
77pub struct MeshClient {
78 inner: mesh_client::MeshClient,
79}
80
81impl MeshClient {
82 pub async fn join(&mut self) -> Result<(), MeshApiError> {
83 self.inner.join().await?;
84 Ok(())
85 }
86
87 pub async fn list_models(&self) -> Result<Vec<Model>, MeshApiError> {
88 Ok(self
89 .inner
90 .list_models()
91 .await?
92 .into_iter()
93 .map(Model::from)
94 .collect())
95 }
96
97 pub fn chat(&self, request: ChatRequest, listener: Arc<dyn EventListener>) -> RequestId {
98 let request_id = self.inner.chat(
99 mesh_client::ChatRequest::from(request),
100 Arc::new(EventListenerAdapter { inner: listener }),
101 );
102 RequestId(request_id.0)
103 }
104
105 pub fn responses(
106 &self,
107 request: ResponsesRequest,
108 listener: Arc<dyn EventListener>,
109 ) -> RequestId {
110 let request_id = self.inner.responses(
111 mesh_client::ResponsesRequest::from(request),
112 Arc::new(EventListenerAdapter { inner: listener }),
113 );
114 RequestId(request_id.0)
115 }
116
117 pub fn cancel(&self, request_id: RequestId) {
118 self.inner.cancel(mesh_client::RequestId(request_id.0));
119 }
120
121 pub async fn status(&self) -> Status {
122 Status::from(self.inner.status().await)
123 }
124
125 pub async fn disconnect(&mut self) {
126 self.inner.disconnect().await;
127 }
128
129 pub async fn reconnect(&mut self) -> Result<(), MeshApiError> {
130 self.inner.reconnect().await?;
131 Ok(())
132 }
133
134 pub fn add_event_listener(&self, listener: Arc<dyn EventListener>) -> String {
135 self.inner
136 .add_event_listener(Arc::new(EventListenerAdapter { inner: listener }))
137 }
138
139 pub fn remove_event_listener(&self, listener_id: &str) {
140 self.inner.remove_event_listener(listener_id);
141 }
142}
143
144#[derive(Clone, Debug)]
145pub struct ChatRequest {
146 pub model: String,
147 pub messages: Vec<ChatMessage>,
148}
149
150impl From<ChatRequest> for mesh_client::ChatRequest {
151 fn from(value: ChatRequest) -> Self {
152 Self {
153 model: value.model,
154 messages: value.messages.into_iter().map(Into::into).collect(),
155 }
156 }
157}
158
159#[derive(Clone, Debug)]
160pub struct ChatMessage {
161 pub role: String,
162 pub content: String,
163}
164
165impl From<ChatMessage> for mesh_client::ChatMessage {
166 fn from(value: ChatMessage) -> Self {
167 Self {
168 role: value.role,
169 content: value.content,
170 }
171 }
172}
173
174#[derive(Clone, Debug)]
175pub struct ResponsesRequest {
176 pub model: String,
177 pub input: String,
178}
179
180impl From<ResponsesRequest> for mesh_client::ResponsesRequest {
181 fn from(value: ResponsesRequest) -> Self {
182 Self {
183 model: value.model,
184 input: value.input,
185 }
186 }
187}
188
189#[derive(Debug, Clone)]
190pub struct Model {
191 pub id: String,
192 pub name: String,
193}
194
195impl From<mesh_client::Model> for Model {
196 fn from(value: mesh_client::Model) -> Self {
197 Self {
198 id: value.id,
199 name: value.name,
200 }
201 }
202}
203
204pub struct Status {
205 pub connected: bool,
206 pub peer_count: usize,
207}
208
209impl From<mesh_client::Status> for Status {
210 fn from(value: mesh_client::Status) -> Self {
211 Self {
212 connected: value.connected,
213 peer_count: value.peer_count,
214 }
215 }
216}
217
218pub struct RequestId(pub String);
219
220impl RequestId {
221 pub fn new() -> Self {
222 Self(mesh_client::RequestId::new().0)
223 }
224}
225
226impl Default for RequestId {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232struct EventListenerAdapter {
233 inner: Arc<dyn EventListener>,
234}
235
236impl mesh_client::events::EventListener for EventListenerAdapter {
237 fn on_event(&self, event: mesh_client::events::Event) {
238 self.inner.on_event(match event {
239 mesh_client::events::Event::Connecting => Event::Connecting,
240 mesh_client::events::Event::Joined { node_id } => Event::Joined { node_id },
241 mesh_client::events::Event::ModelsUpdated { models } => Event::ModelsUpdated {
242 models: models.into_iter().map(Model::from).collect(),
243 },
244 mesh_client::events::Event::TokenDelta { request_id, delta } => {
245 Event::TokenDelta { request_id, delta }
246 }
247 mesh_client::events::Event::Completed { request_id } => Event::Completed { request_id },
248 mesh_client::events::Event::Failed { request_id, error } => {
249 Event::Failed { request_id, error }
250 }
251 mesh_client::events::Event::Disconnected { reason } => Event::Disconnected { reason },
252 });
253 }
254}