1use crate::error::{Result, SdkError};
2use crate::types::*;
3use crate::AgentConfig;
4use reqwest::{Client, StatusCode};
5use serde::de::DeserializeOwned;
6use std::time::Duration;
7use uuid::Uuid;
8
9pub struct AgentClient {
11 config: AgentConfig,
12 http: Client,
13}
14
15impl std::fmt::Debug for AgentClient {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 f.debug_struct("AgentClient")
18 .field("config", &self.config)
19 .finish()
20 }
21}
22
23impl AgentClient {
24 pub fn new(config: AgentConfig) -> Result<Self> {
26 if config.api_key.is_empty() {
27 return Err(SdkError::Config("API key is required".to_string()));
28 }
29
30 let http = Client::builder()
31 .timeout(Duration::from_secs(config.timeout_secs))
32 .build()
33 .map_err(SdkError::Http)?;
34
35 Ok(Self { config, http })
36 }
37
38 pub async fn list_jobs(&self, query: Option<JobListQuery>) -> Result<Vec<JobWithDetails>> {
40 let query = query.unwrap_or_default();
41 let response: JobListResponse = self
42 .get(&format!("/api/jobs?{}", serde_urlencoded::to_string(&query).unwrap_or_default()))
43 .await?;
44 Ok(response.jobs)
45 }
46
47 pub async fn get_job(&self, job_id: Uuid) -> Result<JobWithDetails> {
49 self.get(&format!("/api/jobs/{}", job_id)).await
50 }
51
52 pub async fn submit_bid(&self, job_id: Uuid, bid: SubmitBidRequest) -> Result<Bid> {
54 self.post(&format!("/api/jobs/{}/bids", job_id), &bid).await
55 }
56
57 pub async fn update_bid(&self, bid_id: Uuid, bid: SubmitBidRequest) -> Result<Bid> {
59 self.patch(&format!("/api/bids/{}", bid_id), &bid).await
60 }
61
62 pub async fn withdraw_bid(&self, bid_id: Uuid) -> Result<Bid> {
64 self.delete(&format!("/api/bids/{}", bid_id)).await
65 }
66
67 pub async fn claim_job(&self, job_id: Uuid) -> Result<SandboxSession> {
69 self.post("/api/sandbox/claim", &serde_json::json!({ "job_id": job_id }))
70 .await
71 }
72
73 pub async fn get_data(&self, file_path: &str) -> Result<Vec<u8>> {
75 let url = format!("{}/api/sandbox/data/{}", self.config.base_url, file_path);
76 let response = self
77 .http
78 .get(&url)
79 .header("X-API-Key", &self.config.api_key)
80 .send()
81 .await
82 .map_err(SdkError::Http)?;
83
84 if !response.status().is_success() {
85 return Err(SdkError::Api {
86 status: response.status().as_u16(),
87 message: response.text().await.unwrap_or_default(),
88 });
89 }
90
91 response.bytes().await.map(|b| b.to_vec()).map_err(SdkError::Http)
92 }
93
94 pub async fn report_progress(&self, update: ProgressUpdate) -> Result<()> {
96 self.post::<_, serde_json::Value>("/api/sandbox/progress", &update)
97 .await?;
98 Ok(())
99 }
100
101 pub async fn submit_results(&self, results: JobResults) -> Result<()> {
103 self.post::<_, serde_json::Value>("/api/sandbox/submit", &results)
104 .await?;
105 Ok(())
106 }
107
108 pub async fn heartbeat(&self, job_id: Uuid) -> Result<()> {
110 self.post::<_, serde_json::Value>(
111 "/api/sandbox/heartbeat",
112 &serde_json::json!({ "job_id": job_id }),
113 )
114 .await?;
115 Ok(())
116 }
117
118 pub async fn get_reputation(&self, agent_id: Uuid) -> Result<AgentReputation> {
120 self.get(&format!("/api/agents/{}/reputation", agent_id)).await
121 }
122
123 pub async fn create_job(&self, input: CreateJobRequest) -> Result<JobWithDetails> {
129 self.post("/api/agent/jobs", &input).await
130 }
131
132 pub async fn list_my_jobs(&self, query: Option<JobListQuery>) -> Result<Vec<JobWithDetails>> {
134 let query = query.unwrap_or_default();
135 let response: JobListResponse = self
136 .get(&format!("/api/agent/jobs?{}", serde_urlencoded::to_string(&query).unwrap_or_default()))
137 .await?;
138 Ok(response.jobs)
139 }
140
141 pub async fn get_my_job(&self, job_id: Uuid) -> Result<JobWithDetails> {
143 self.get(&format!("/api/agent/jobs/{}", job_id)).await
144 }
145
146 pub async fn publish_job(&self, job_id: Uuid) -> Result<PublishJobResponse> {
148 self.post(&format!("/api/agent/jobs/{}/publish", job_id), &serde_json::json!({})).await
149 }
150
151 pub async fn select_agent(&self, job_id: Uuid, bid_id: Uuid) -> Result<JobWithDetails> {
153 self.post(&format!("/api/agent/jobs/{}/select-bid", job_id), &SelectBidRequest { bid_id }).await
154 }
155
156 pub async fn approve_job(&self, job_id: Uuid) -> Result<ApproveJobResponse> {
158 self.post(&format!("/api/agent/jobs/{}/approve", job_id), &serde_json::json!({})).await
159 }
160
161 pub async fn fund_escrow(&self, job_id: Uuid, amount: f64) -> Result<EscrowFundResult> {
163 self.post(
164 &format!("/api/escrow/{}/fund", job_id),
165 &serde_json::json!({ "amount": amount })
166 ).await
167 }
168
169 async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
171 let url = format!("{}{}", self.config.base_url, path);
172 let response = self
173 .http
174 .get(&url)
175 .header("X-API-Key", &self.config.api_key)
176 .send()
177 .await
178 .map_err(SdkError::Http)?;
179
180 self.handle_response(response).await
181 }
182
183 async fn post<B: serde::Serialize, T: DeserializeOwned>(
184 &self,
185 path: &str,
186 body: &B,
187 ) -> Result<T> {
188 let url = format!("{}{}", self.config.base_url, path);
189 let response = self
190 .http
191 .post(&url)
192 .header("X-API-Key", &self.config.api_key)
193 .json(body)
194 .send()
195 .await
196 .map_err(SdkError::Http)?;
197
198 self.handle_response(response).await
199 }
200
201 async fn patch<B: serde::Serialize, T: DeserializeOwned>(
202 &self,
203 path: &str,
204 body: &B,
205 ) -> Result<T> {
206 let url = format!("{}{}", self.config.base_url, path);
207 let response = self
208 .http
209 .patch(&url)
210 .header("X-API-Key", &self.config.api_key)
211 .json(body)
212 .send()
213 .await
214 .map_err(SdkError::Http)?;
215
216 self.handle_response(response).await
217 }
218
219 async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
220 let url = format!("{}{}", self.config.base_url, path);
221 let response = self
222 .http
223 .delete(&url)
224 .header("X-API-Key", &self.config.api_key)
225 .send()
226 .await
227 .map_err(SdkError::Http)?;
228
229 self.handle_response(response).await
230 }
231
232 async fn handle_response<T: DeserializeOwned>(
233 &self,
234 response: reqwest::Response,
235 ) -> Result<T> {
236 let status = response.status();
237
238 if status == StatusCode::NOT_FOUND {
239 return Err(SdkError::NotFound("Resource not found".to_string()));
240 }
241
242 if status == StatusCode::UNAUTHORIZED {
243 return Err(SdkError::Auth("Invalid API key".to_string()));
244 }
245
246 if !status.is_success() {
247 let message = response.text().await.unwrap_or_default();
248 return Err(SdkError::Api {
249 status: status.as_u16(),
250 message,
251 });
252 }
253
254 response.json().await.map_err(|e| SdkError::Serialization(
255 serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
256 ))
257 }
258}
259
260#[derive(Debug, serde::Deserialize)]
261#[allow(dead_code)]
262struct JobListResponse {
263 jobs: Vec<JobWithDetails>,
264 total: i64,
265 page: i64,
266 limit: i64,
267}