1use serde::{Deserialize, Serialize};
2
3use crate::chat::ChatRequest;
4use crate::client::Client;
5use crate::error::Result;
6
7#[derive(Debug, Clone, Serialize)]
9pub struct JobCreateRequest {
10 #[serde(rename = "type")]
12 pub job_type: String,
13
14 pub params: serde_json::Value,
16}
17
18#[derive(Debug, Clone, Deserialize)]
20pub struct JobCreateResponse {
21 pub job_id: String,
22 #[serde(default)]
23 pub status: String,
24 #[serde(rename = "type", default)]
25 pub job_type: Option<String>,
26 #[serde(default)]
27 pub request_id: Option<String>,
28 #[serde(default)]
29 pub created_at: Option<String>,
30}
31
32#[derive(Debug, Clone, Deserialize)]
34pub struct JobStatusResponse {
35 pub job_id: String,
36 pub status: String,
37 #[serde(rename = "type", default)]
38 pub job_type: Option<String>,
39 #[serde(default)]
40 pub result: Option<serde_json::Value>,
41 #[serde(default)]
42 pub error: Option<String>,
43 #[serde(default)]
44 pub cost_ticks: i64,
45 #[serde(default)]
46 pub request_id: Option<String>,
47 #[serde(default)]
48 pub created_at: Option<String>,
49 #[serde(default)]
50 pub started_at: Option<String>,
51 #[serde(default)]
52 pub completed_at: Option<String>,
53}
54
55#[derive(Debug, Clone, Deserialize)]
57pub struct JobSummary {
58 pub job_id: String,
59 pub status: String,
60 #[serde(rename = "type", default)]
61 pub job_type: Option<String>,
62 #[serde(default)]
63 pub created_at: Option<String>,
64 #[serde(default)]
65 pub completed_at: Option<String>,
66 #[serde(default)]
67 pub cost_ticks: i64,
68}
69
70#[derive(Debug, Clone, Deserialize)]
72pub struct ListJobsResponse {
73 pub jobs: Vec<JobSummary>,
74}
75
76#[derive(Debug, Clone, Deserialize)]
78pub struct JobStreamEvent {
79 #[serde(rename = "type", default)]
81 pub event_type: String,
82
83 #[serde(default)]
85 pub job_id: Option<String>,
86
87 #[serde(default)]
89 pub status: Option<String>,
90
91 #[serde(default)]
93 pub result: Option<serde_json::Value>,
94
95 #[serde(default)]
97 pub error: Option<String>,
98
99 #[serde(default)]
101 pub cost_ticks: i64,
102
103 #[serde(default)]
105 pub completed_at: Option<String>,
106}
107
108#[derive(Debug, Clone, Deserialize)]
111pub struct JobAcceptedResponse {
112 pub job_id: String,
114
115 #[serde(default)]
117 pub status: String,
118
119 #[serde(rename = "type", default)]
121 pub job_type: Option<String>,
122
123 #[serde(default)]
125 pub request_id: Option<String>,
126}
127
128#[derive(Debug, Clone, Deserialize)]
130pub struct JobListEntry {
131 pub job_id: String,
133
134 #[serde(rename = "type", default)]
136 pub job_type: Option<String>,
137
138 pub status: String,
140
141 #[serde(default)]
143 pub result: Option<serde_json::Value>,
144
145 #[serde(default)]
147 pub error: Option<String>,
148
149 #[serde(default)]
151 pub cost_ticks: i64,
152
153 #[serde(default)]
155 pub created_at: Option<String>,
156
157 #[serde(default)]
159 pub started_at: Option<String>,
160
161 #[serde(default)]
163 pub completed_at: Option<String>,
164
165 #[serde(default)]
167 pub request_id: Option<String>,
168}
169
170#[derive(Debug, Clone, Deserialize)]
172pub struct JobListResponse {
173 pub jobs: Vec<JobListEntry>,
175
176 #[serde(default)]
178 pub request_id: Option<String>,
179}
180
181impl Client {
182 pub async fn create_job(&self, req: &JobCreateRequest) -> Result<JobCreateResponse> {
184 let (resp, _meta) = self
185 .post_json::<JobCreateRequest, JobCreateResponse>("/qai/v1/jobs", req)
186 .await?;
187 Ok(resp)
188 }
189
190 pub async fn get_job(&self, job_id: &str) -> Result<JobStatusResponse> {
192 let path = format!("/qai/v1/jobs/{job_id}");
193 let (resp, _meta) = self.get_json::<JobStatusResponse>(&path).await?;
194 Ok(resp)
195 }
196
197 pub async fn list_jobs(&self) -> Result<ListJobsResponse> {
199 let (resp, _meta) = self
200 .get_json::<ListJobsResponse>("/qai/v1/jobs")
201 .await?;
202 Ok(resp)
203 }
204
205 pub async fn stream_job(
208 &self,
209 job_id: &str,
210 ) -> Result<reqwest::Response> {
211 let path = format!("/qai/v1/jobs/{job_id}/stream");
212 let (resp, _meta) = self.get_stream_raw(&path).await?;
213 Ok(resp)
214 }
215
216 pub async fn poll_job(
219 &self,
220 job_id: &str,
221 poll_interval: std::time::Duration,
222 max_attempts: usize,
223 ) -> Result<JobStatusResponse> {
224 for _ in 0..max_attempts {
225 tokio::time::sleep(poll_interval).await;
226 let status = self.get_job(job_id).await?;
227 match status.status.as_str() {
228 "completed" | "failed" => return Ok(status),
229 _ => continue,
230 }
231 }
232 Ok(JobStatusResponse {
233 job_id: job_id.to_string(),
234 status: "timeout".to_string(),
235 job_type: None,
236 result: None,
237 error: Some(format!("Job polling timed out after {max_attempts} attempts")),
238 cost_ticks: 0,
239 request_id: None,
240 created_at: None,
241 started_at: None,
242 completed_at: None,
243 })
244 }
245
246 pub async fn generate_3d(
251 &self,
252 model: &str,
253 prompt: Option<&str>,
254 image_url: Option<&str>,
255 ) -> Result<JobCreateResponse> {
256 let mut params = serde_json::json!({ "model": model });
257 if let Some(p) = prompt {
258 params["prompt"] = serde_json::Value::String(p.to_string());
259 }
260 if let Some(u) = image_url {
261 params["image_url"] = serde_json::Value::String(u.to_string());
262 }
263 let req = JobCreateRequest {
264 job_type: "3d/generate".to_string(),
265 params,
266 };
267 self.create_job(&req).await
268 }
269
270 pub async fn chat_job(&self, req: &ChatRequest) -> Result<JobCreateResponse> {
276 let params = serde_json::to_value(req)?;
277 let job_req = JobCreateRequest {
278 job_type: "chat".to_string(),
279 params,
280 };
281 self.create_job(&job_req).await
282 }
283}