1use crate::types::common::Content;
2use base64::engine::general_purpose::STANDARD as base64_engine;
3use base64::Engine as _;
4use serde_json::json;
5use std::path::Path;
6
7pub fn content_to_provider_value(content: &Content) -> serde_json::Value {
14 match content {
15 Content::Text(s) => serde_json::Value::String(s.clone()),
16 Content::Json(v) => v.clone(),
17 Content::Image {
18 url,
19 mime: _mime,
20 name,
21 } => {
22 if let Some(u) = url {
23 json!({"image": {"url": u}})
24 } else {
25 if let Some(n) = name {
27 if let Some(data_url) = upload_file_inline(n, _mime.as_deref()) {
28 json!({"image": {"data": data_url}})
29 } else {
30 json!({"image": {"note": "no url, name not a readable path"}})
31 }
32 } else {
33 json!({"image": {"note": "no url"}})
34 }
35 }
36 }
37 Content::Audio { url, mime: _mime } => {
38 if let Some(u) = url {
39 json!({"audio": {"url": u}})
40 } else {
41 json!({"audio": {"note": "no url"}})
42 }
43 }
44 }
45}
46use crate::types::AiLibError;
47use reqwest::Client;
48use std::fs;
49use std::time::Duration;
50
51pub fn upload_file_inline(path: &str, mime: Option<&str>) -> Option<String> {
54 let p = Path::new(path);
55 if !p.exists() {
56 return None;
57 }
58 if let Ok(bytes) = fs::read(p) {
59 let b64 = base64_engine.encode(bytes);
60 let mime = mime.unwrap_or("application/octet-stream");
61 Some(format!("data:{};base64,{}", mime, b64))
62 } else {
63 None
64 }
65}
66
67pub async fn upload_file_to_provider(
71 upload_url: &str,
72 path: &str,
73 field_name: &str,
74) -> Result<String, crate::types::AiLibError> {
75 use reqwest::multipart;
76
77 let p = Path::new(path);
78 if !p.exists() {
79 return Err(crate::types::AiLibError::ProviderError(
80 "file not found".to_string(),
81 ));
82 }
83
84 let file_name = p.file_name().and_then(|n| n.to_str()).unwrap_or("file.bin");
85 let bytes = fs::read(p)
86 .map_err(|e| crate::types::AiLibError::ProviderError(format!("read error: {}", e)))?;
87
88 let part = multipart::Part::bytes(bytes).file_name(file_name.to_string());
89 let form = multipart::Form::new().part(field_name.to_string(), part);
90
91 let client_builder = reqwest::Client::builder();
94 let client_builder = if let Ok(proxy_url) = std::env::var("AI_PROXY_URL") {
95 if let Ok(proxy) = reqwest::Proxy::all(&proxy_url) {
96 client_builder.proxy(proxy)
97 } else {
98 client_builder.no_proxy()
99 }
100 } else {
101 client_builder.no_proxy()
102 };
103
104 let client = client_builder.build().map_err(|e| {
105 crate::types::AiLibError::NetworkError(format!("failed to build http client: {}", e))
106 })?;
107 let resp = client
108 .post(upload_url)
109 .multipart(form)
110 .send()
111 .await
112 .map_err(|e| crate::types::AiLibError::NetworkError(format!("upload failed: {}", e)))?;
113 let status = resp.status();
114 if !status.is_success() {
115 let txt = resp.text().await.unwrap_or_default();
116 return Err(crate::types::AiLibError::ProviderError(format!(
117 "upload returned {}: {}",
118 status, txt
119 )));
120 }
121
122 let j: serde_json::Value = resp
124 .json()
125 .await
126 .map_err(|e| crate::types::AiLibError::ProviderError(format!("parse response: {}", e)))?;
127 parse_upload_response(j)
128}
129
130pub async fn upload_file_with_transport(
134 transport: Option<crate::transport::dyn_transport::DynHttpTransportRef>,
135 upload_url: &str,
136 path: &str,
137 field_name: &str,
138) -> Result<String, crate::types::AiLibError> {
139 use std::fs;
140 use std::path::Path;
141
142 let p = Path::new(path);
143 if !p.exists() {
144 return Err(crate::types::AiLibError::ProviderError(
145 "file not found".to_string(),
146 ));
147 }
148 let file_name = p
149 .file_name()
150 .and_then(|n| n.to_str())
151 .unwrap_or("file.bin")
152 .to_string();
153 let bytes = fs::read(p)
154 .map_err(|e| crate::types::AiLibError::ProviderError(format!("read error: {}", e)))?;
155
156 if let Some(t) = transport {
157 let headers = None;
159 let j = t
160 .upload_multipart(upload_url, headers, field_name, &file_name, bytes)
161 .await?;
162 return parse_upload_response(j);
163 }
164
165 upload_file_to_provider(upload_url, path, field_name).await
167}
168
169pub(crate) fn parse_upload_response(
172 j: serde_json::Value,
173) -> Result<String, crate::types::AiLibError> {
174 if let Some(url) = j.get("url").and_then(|v| v.as_str()) {
175 return Ok(url.to_string());
176 }
177 if let Some(id) = j.get("id").and_then(|v| v.as_str()) {
178 return Ok(id.to_string());
179 }
180 Err(crate::types::AiLibError::ProviderError(
181 "upload response missing url/id".to_string(),
182 ))
183}
184
185pub async fn health_check(base_url: &str) -> Result<(), AiLibError> {
189 let client_builder = Client::builder().timeout(Duration::from_secs(5));
190
191 let client_builder = if let Ok(proxy_url) = std::env::var("AI_PROXY_URL") {
193 if let Ok(proxy) = reqwest::Proxy::all(&proxy_url) {
194 client_builder.proxy(proxy)
195 } else {
196 client_builder
197 }
198 } else {
199 client_builder
200 };
201
202 let client = client_builder
203 .build()
204 .map_err(|e| AiLibError::NetworkError(format!("Failed to build HTTP client: {}", e)))?;
205
206 let models_url = if base_url.ends_with('/') {
208 format!("{}models", base_url)
209 } else {
210 format!("{}/models", base_url)
211 };
212
213 let resp = client.get(&models_url).send().await;
214 match resp {
215 Ok(r) if r.status().is_success() => Ok(()),
216 _ => {
217 let resp2 = client.get(base_url).send().await;
219 match resp2 {
220 Ok(r2) if r2.status().is_success() => Ok(()),
221 Ok(r2) => Err(AiLibError::NetworkError(format!(
222 "Health check returned status {}",
223 r2.status()
224 ))),
225 Err(e) => Err(AiLibError::NetworkError(format!(
226 "Health check request failed: {}",
227 e
228 ))),
229 }
230 }
231 }
232}
233
234#[cfg(test)]
237mod tests {
238 use super::*;
239 use serde_json::json;
240
241 #[test]
242 fn parse_upload_response_url() {
243 let j = json!({"url": "https://cdn.example.com/file.png"});
244 let res = parse_upload_response(j).unwrap();
245 assert_eq!(res, "https://cdn.example.com/file.png");
246 }
247
248 #[test]
249 fn parse_upload_response_id() {
250 let j = json!({"id": "file_123"});
251 let res = parse_upload_response(j).unwrap();
252 assert_eq!(res, "file_123");
253 }
254}