Skip to main content

quantum_sdk/
jobs.rs

1use serde::{Deserialize, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6/// Request to create an async job.
7#[derive(Debug, Clone, Serialize)]
8pub struct JobCreateRequest {
9    /// Job type (e.g. "video/generate", "audio/music").
10    #[serde(rename = "type")]
11    pub job_type: String,
12
13    /// Job parameters (model-specific).
14    pub params: serde_json::Value,
15}
16
17/// Response from job creation.
18#[derive(Debug, Clone, Deserialize)]
19pub struct JobCreateResponse {
20    pub job_id: String,
21    #[serde(default)]
22    pub status: String,
23}
24
25/// Response from job status check.
26#[derive(Debug, Clone, Deserialize)]
27pub struct JobStatusResponse {
28    pub job_id: String,
29    pub status: String,
30    #[serde(default)]
31    pub result: Option<serde_json::Value>,
32    #[serde(default)]
33    pub error: Option<String>,
34    #[serde(default)]
35    pub cost_ticks: i64,
36}
37
38/// Summary of a job in the list response.
39#[derive(Debug, Clone, Deserialize)]
40pub struct JobSummary {
41    pub job_id: String,
42    pub status: String,
43    #[serde(rename = "type", default)]
44    pub job_type: Option<String>,
45    #[serde(default)]
46    pub created_at: Option<String>,
47    #[serde(default)]
48    pub completed_at: Option<String>,
49    #[serde(default)]
50    pub cost_ticks: i64,
51}
52
53/// Response from listing jobs.
54#[derive(Debug, Clone, Deserialize)]
55pub struct ListJobsResponse {
56    pub jobs: Vec<JobSummary>,
57}
58
59impl Client {
60    /// Creates an async job. Returns the job ID for polling.
61    pub async fn create_job(&self, req: &JobCreateRequest) -> Result<JobCreateResponse> {
62        let (resp, _meta) = self
63            .post_json::<JobCreateRequest, JobCreateResponse>("/qai/v1/jobs", req)
64            .await?;
65        Ok(resp)
66    }
67
68    /// Checks the status of an async job.
69    pub async fn get_job(&self, job_id: &str) -> Result<JobStatusResponse> {
70        let path = format!("/qai/v1/jobs/{job_id}");
71        let (resp, _meta) = self.get_json::<JobStatusResponse>(&path).await?;
72        Ok(resp)
73    }
74
75    /// Lists all jobs for the account.
76    pub async fn list_jobs(&self) -> Result<ListJobsResponse> {
77        let (resp, _meta) = self
78            .get_json::<ListJobsResponse>("/qai/v1/jobs")
79            .await?;
80        Ok(resp)
81    }
82
83    /// Opens an SSE stream for a job, returning the raw response.
84    /// Events: progress, complete, error. Auto-closes on terminal state.
85    pub async fn stream_job(
86        &self,
87        job_id: &str,
88    ) -> Result<reqwest::Response> {
89        let path = format!("/qai/v1/jobs/{job_id}/stream");
90        let (resp, _meta) = self.get_stream_raw(&path).await?;
91        Ok(resp)
92    }
93
94    /// Polls a job until completion or timeout.
95    /// Returns the final status response.
96    pub async fn poll_job(
97        &self,
98        job_id: &str,
99        poll_interval: std::time::Duration,
100        max_attempts: usize,
101    ) -> Result<JobStatusResponse> {
102        for _ in 0..max_attempts {
103            tokio::time::sleep(poll_interval).await;
104            let status = self.get_job(job_id).await?;
105            match status.status.as_str() {
106                "completed" | "failed" => return Ok(status),
107                _ => continue,
108            }
109        }
110        Ok(JobStatusResponse {
111            job_id: job_id.to_string(),
112            status: "timeout".to_string(),
113            result: None,
114            error: Some(format!("Job polling timed out after {max_attempts} attempts")),
115            cost_ticks: 0,
116        })
117    }
118
119    /// Convenience method for 3D model generation via the async jobs system.
120    ///
121    /// Submits a job with type `"3d/generate"` and the given parameters.
122    /// Returns the job creation response -- use `poll_job` to wait for completion.
123    pub async fn generate_3d(
124        &self,
125        model: &str,
126        prompt: Option<&str>,
127        image_url: Option<&str>,
128    ) -> Result<JobCreateResponse> {
129        let mut params = serde_json::json!({ "model": model });
130        if let Some(p) = prompt {
131            params["prompt"] = serde_json::Value::String(p.to_string());
132        }
133        if let Some(u) = image_url {
134            params["image_url"] = serde_json::Value::String(u.to_string());
135        }
136        let req = JobCreateRequest {
137            job_type: "3d/generate".to_string(),
138            params,
139        };
140        self.create_job(&req).await
141    }
142}