watsonx_rs/orchestrate/
client.rs1use crate::error::{Error, Result};
7use super::types::*;
8use super::config::OrchestrateConfig;
9use reqwest::{Client, ClientBuilder};
10use serde_json::Value;
11use std::collections::HashMap;
12use std::time::Duration;
13
14pub struct OrchestrateClient {
16 pub(crate) config: OrchestrateConfig,
17 pub(crate) access_token: Option<String>,
18 pub(crate) client: Client,
19}
20
21impl OrchestrateClient {
22 pub fn new(config: OrchestrateConfig) -> Self {
24 let client = ClientBuilder::new()
25 .timeout(Duration::from_secs(300)) .tcp_keepalive(Duration::from_secs(60))
27 .http1_title_case_headers()
28 .build()
29 .unwrap_or_else(|_| Client::new());
30
31 Self {
32 config,
33 access_token: None,
34 client,
35 }
36 }
37
38 pub fn with_token(mut self, token: String) -> Self {
40 self.access_token = Some(token);
41 self
42 }
43
44 pub fn set_token(&mut self, token: String) {
46 self.access_token = Some(token);
47 }
48
49 pub fn config(&self) -> &OrchestrateConfig {
51 &self.config
52 }
53
54 pub fn is_authenticated(&self) -> bool {
56 self.access_token.is_some()
57 }
58
59 pub async fn generate_jwt_token(api_key: &str) -> Result<String> {
62 let client = reqwest::Client::new();
63 let body = format!(
64 "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={}",
65 api_key
66 );
67
68 let response = client
69 .post("https://iam.cloud.ibm.com/identity/token")
70 .header("Content-Type", "application/x-www-form-urlencoded")
71 .body(body)
72 .send()
73 .await
74 .map_err(|e| Error::Network(format!("Failed to generate IAM token: {}", e)))?;
75
76 if !response.status().is_success() {
77 let status = response.status();
78 let error_text = response
79 .text()
80 .await
81 .unwrap_or_else(|_| "Unknown error".to_string());
82 return Err(Error::Api(format!(
83 "Failed to generate IAM token: {} - {}",
84 status, error_text
85 )));
86 }
87
88 #[derive(serde::Deserialize)]
89 struct TokenResponse {
90 access_token: String,
91 }
92
93 let token_response: TokenResponse = response
94 .json()
95 .await
96 .map_err(|e| Error::Serialization(format!("Failed to parse IAM token response: {}", e)))?;
97
98 Ok(token_response.access_token)
99 }
100
101 pub async fn list_assistants(&self) -> Result<Vec<CustomAssistant>> {
107 let access_token = self.access_token.as_ref().ok_or_else(|| {
108 Error::Authentication("Not authenticated. Set access token first.".to_string())
109 })?;
110
111 let url = format!(
112 "{}/v1/assistants",
113 self.config.get_base_url()
114 );
115
116 let response = self
117 .client
118 .get(&url)
119 .header("Authorization", format!("Bearer {}", access_token))
120 .header("Content-Type", "application/json")
121 .send()
122 .await
123 .map_err(|e| Error::Network(e.to_string()))?;
124
125 if !response.status().is_success() {
126 let status = response.status();
127 let error_text = response
128 .text()
129 .await
130 .unwrap_or_else(|_| "Unknown error".to_string());
131 return Err(Error::Api(format!(
132 "Failed to list assistants: {} - {}",
133 status, error_text
134 )));
135 }
136
137 let text = response
138 .text()
139 .await
140 .map_err(|e| Error::Serialization(e.to_string()))?;
141
142 if let Ok(assistants) = serde_json::from_str::<Vec<CustomAssistant>>(&text) {
144 return Ok(assistants);
145 }
146
147 if let Ok(obj) = serde_json::from_str::<serde_json::Value>(&text) {
149 if let Some(assistants_array) = obj.get("assistants").and_then(|a| a.as_array()) {
150 let assistants: Result<Vec<CustomAssistant>> = assistants_array
151 .iter()
152 .map(|item| {
153 serde_json::from_value::<CustomAssistant>(item.clone())
154 .map_err(|e| Error::Serialization(e.to_string()))
155 })
156 .collect();
157 return assistants;
158 }
159 }
160
161 Ok(Vec::new())
162 }
163
164 pub async fn send_batch_messages(&self, request: BatchMessageRequest) -> Result<BatchMessageResponse> {
166 let api_key = self.access_token.as_ref().ok_or_else(|| {
167 Error::Authentication("Not authenticated. Set access token (API key) first.".to_string())
168 })?;
169
170 let base_url = self.config.get_base_url();
171 let url = format!("{}/batch/messages", base_url);
172
173 let response = self
174 .client
175 .post(&url)
176 .header("Authorization", format!("Bearer {}", api_key))
177 .header("Content-Type", "application/json")
178 .json(&request)
179 .send()
180 .await
181 .map_err(|e| Error::Network(e.to_string()))?;
182
183 if !response.status().is_success() {
184 let status = response.status();
185 let error_text = response
186 .text()
187 .await
188 .unwrap_or_else(|_| "Unknown error".to_string());
189 return Err(Error::Api(format!(
190 "Failed to send batch messages: {} - {}",
191 status, error_text
192 )));
193 }
194
195 let batch_response: BatchMessageResponse = response
196 .json()
197 .await
198 .map_err(|e| Error::Serialization(e.to_string()))?;
199
200 Ok(batch_response)
201 }
202
203 pub async fn list_skills(&self) -> Result<Vec<Skill>> {
209 let api_key = self.access_token.as_ref().ok_or_else(|| {
210 Error::Authentication("Not authenticated. Set access token (API key) first.".to_string())
211 })?;
212
213 let base_url = self.config.get_base_url();
214 let url = format!("{}/skills", base_url);
215
216 let response = self
217 .client
218 .get(&url)
219 .header("Authorization", format!("Bearer {}", api_key))
220 .header("Content-Type", "application/json")
221 .send()
222 .await
223 .map_err(|e| Error::Network(e.to_string()))?;
224
225 if !response.status().is_success() {
226 let status = response.status();
227 let error_text = response
228 .text()
229 .await
230 .unwrap_or_else(|_| "Unknown error".to_string());
231 return Err(Error::Api(format!(
232 "Failed to list skills: {} - {}",
233 status, error_text
234 )));
235 }
236
237 let text = response
238 .text()
239 .await
240 .map_err(|e| Error::Serialization(e.to_string()))?;
241
242 if let Ok(skills) = serde_json::from_str::<Vec<Skill>>(&text) {
243 return Ok(skills);
244 }
245
246 if let Ok(obj) = serde_json::from_str::<serde_json::Value>(&text) {
247 if let Some(skills_array) = obj.get("skills").and_then(|s| s.as_array()) {
248 let skills: Result<Vec<Skill>> = skills_array
249 .iter()
250 .map(|skill| {
251 serde_json::from_value::<Skill>(skill.clone())
252 .map_err(|e| Error::Serialization(e.to_string()))
253 })
254 .collect();
255 return skills;
256 }
257 }
258
259 Ok(Vec::new())
260 }
261
262 pub async fn get_skill(&self, skill_id: &str) -> Result<Skill> {
264 let api_key = self.access_token.as_ref().ok_or_else(|| {
265 Error::Authentication("Not authenticated. Set access token (API key) first.".to_string())
266 })?;
267
268 let base_url = self.config.get_base_url();
269 let url = format!("{}/skills/{}", base_url, skill_id);
270
271 let response = self
272 .client
273 .get(&url)
274 .header("Authorization", format!("Bearer {}", api_key))
275 .header("Content-Type", "application/json")
276 .send()
277 .await
278 .map_err(|e| Error::Network(e.to_string()))?;
279
280 if !response.status().is_success() {
281 let status = response.status();
282 let error_text = response
283 .text()
284 .await
285 .unwrap_or_else(|_| "Unknown error".to_string());
286 return Err(Error::Api(format!(
287 "Failed to get skill {}: {} - {}",
288 skill_id, status, error_text
289 )));
290 }
291
292 let skill: Skill = response
293 .json()
294 .await
295 .map_err(|e| Error::Serialization(e.to_string()))?;
296
297 Ok(skill)
298 }
299}
300
301#[derive(serde::Deserialize)]
303struct ChatChunk {
304 content: Option<String>,
305 metadata: Option<HashMap<String, Value>>,
306}
307
308#[derive(serde::Deserialize)]
309struct EventData {
310 event: String,
311 data: Value,
312}