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}
25
26#[derive(Debug, Clone, Deserialize)]
28pub struct JobStatusResponse {
29 pub job_id: String,
30 pub status: String,
31 #[serde(default)]
32 pub result: Option<serde_json::Value>,
33 #[serde(default)]
34 pub error: Option<String>,
35 #[serde(default)]
36 pub cost_ticks: i64,
37}
38
39#[derive(Debug, Clone, Deserialize)]
41pub struct JobSummary {
42 pub job_id: String,
43 pub status: String,
44 #[serde(rename = "type", default)]
45 pub job_type: Option<String>,
46 #[serde(default)]
47 pub created_at: Option<String>,
48 #[serde(default)]
49 pub completed_at: Option<String>,
50 #[serde(default)]
51 pub cost_ticks: i64,
52}
53
54#[derive(Debug, Clone, Deserialize)]
56pub struct ListJobsResponse {
57 pub jobs: Vec<JobSummary>,
58}
59
60#[derive(Debug, Clone, Deserialize)]
62pub struct JobStreamEvent {
63 #[serde(rename = "type", default)]
65 pub event_type: String,
66
67 #[serde(default)]
69 pub job_id: Option<String>,
70
71 #[serde(default)]
73 pub status: Option<String>,
74
75 #[serde(default)]
77 pub result: Option<serde_json::Value>,
78
79 #[serde(default)]
81 pub error: Option<String>,
82
83 #[serde(default)]
85 pub cost_ticks: i64,
86
87 #[serde(default)]
89 pub completed_at: Option<String>,
90}
91
92#[derive(Debug, Clone, Deserialize)]
95pub struct JobAcceptedResponse {
96 pub job_id: String,
98
99 #[serde(default)]
101 pub status: String,
102
103 #[serde(rename = "type", default)]
105 pub job_type: Option<String>,
106
107 #[serde(default)]
109 pub request_id: Option<String>,
110}
111
112#[derive(Debug, Clone, Deserialize)]
114pub struct JobListEntry {
115 pub job_id: String,
117
118 #[serde(rename = "type", default)]
120 pub job_type: Option<String>,
121
122 pub status: String,
124
125 #[serde(default)]
127 pub result: Option<serde_json::Value>,
128
129 #[serde(default)]
131 pub error: Option<String>,
132
133 #[serde(default)]
135 pub cost_ticks: i64,
136
137 #[serde(default)]
139 pub created_at: Option<String>,
140
141 #[serde(default)]
143 pub started_at: Option<String>,
144
145 #[serde(default)]
147 pub completed_at: Option<String>,
148
149 #[serde(default)]
151 pub request_id: Option<String>,
152}
153
154#[derive(Debug, Clone, Deserialize)]
156pub struct JobListResponse {
157 pub jobs: Vec<JobListEntry>,
159
160 #[serde(default)]
162 pub request_id: Option<String>,
163}
164
165impl Client {
166 pub async fn create_job(&self, req: &JobCreateRequest) -> Result<JobCreateResponse> {
168 let (resp, _meta) = self
169 .post_json::<JobCreateRequest, JobCreateResponse>("/qai/v1/jobs", req)
170 .await?;
171 Ok(resp)
172 }
173
174 pub async fn get_job(&self, job_id: &str) -> Result<JobStatusResponse> {
176 let path = format!("/qai/v1/jobs/{job_id}");
177 let (resp, _meta) = self.get_json::<JobStatusResponse>(&path).await?;
178 Ok(resp)
179 }
180
181 pub async fn list_jobs(&self) -> Result<ListJobsResponse> {
183 let (resp, _meta) = self
184 .get_json::<ListJobsResponse>("/qai/v1/jobs")
185 .await?;
186 Ok(resp)
187 }
188
189 pub async fn stream_job(
192 &self,
193 job_id: &str,
194 ) -> Result<reqwest::Response> {
195 let path = format!("/qai/v1/jobs/{job_id}/stream");
196 let (resp, _meta) = self.get_stream_raw(&path).await?;
197 Ok(resp)
198 }
199
200 pub async fn poll_job(
203 &self,
204 job_id: &str,
205 poll_interval: std::time::Duration,
206 max_attempts: usize,
207 ) -> Result<JobStatusResponse> {
208 for _ in 0..max_attempts {
209 tokio::time::sleep(poll_interval).await;
210 let status = self.get_job(job_id).await?;
211 match status.status.as_str() {
212 "completed" | "failed" => return Ok(status),
213 _ => continue,
214 }
215 }
216 Ok(JobStatusResponse {
217 job_id: job_id.to_string(),
218 status: "timeout".to_string(),
219 result: None,
220 error: Some(format!("Job polling timed out after {max_attempts} attempts")),
221 cost_ticks: 0,
222 })
223 }
224
225 pub async fn generate_3d(
230 &self,
231 model: &str,
232 prompt: Option<&str>,
233 image_url: Option<&str>,
234 ) -> Result<JobCreateResponse> {
235 let mut params = serde_json::json!({ "model": model });
236 if let Some(p) = prompt {
237 params["prompt"] = serde_json::Value::String(p.to_string());
238 }
239 if let Some(u) = image_url {
240 params["image_url"] = serde_json::Value::String(u.to_string());
241 }
242 let req = JobCreateRequest {
243 job_type: "3d/generate".to_string(),
244 params,
245 };
246 self.create_job(&req).await
247 }
248
249 pub async fn chat_job(&self, req: &ChatRequest) -> Result<JobCreateResponse> {
255 let params = serde_json::to_value(req)?;
256 let job_req = JobCreateRequest {
257 job_type: "chat".to_string(),
258 params,
259 };
260 self.create_job(&job_req).await
261 }
262}