From 2ea93762450ebda38cd74b5e2a0dca3591aa8ffb Mon Sep 17 00:00:00 2001 From: Jonathan Richard Date: Tue, 6 Oct 2026 20:50:57 -0400 Subject: [PATCH 1/4] feat: job reads, cancel and the queued job number - `list_jobs`, `get_job`, `get_job_logs` (byte-offset pages) and `cancel_job` mirror the server's project job routes; responses derive Serialize. - `start_remote_job` returns the queue response, including `job_num` when the server sends it. - `ApiErrorCode::ComputeProviderJob` names a missing job. --- tracel-client/src/console/job/mod.rs | 85 ++++++++++++++++++++-- tracel-client/src/console/job/response.rs | 87 +++++++++++++++++++++++ tracel-client/src/error.rs | 1 + tracel-client/src/transport.rs | 7 ++ 4 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 tracel-client/src/console/job/response.rs diff --git a/tracel-client/src/console/job/mod.rs b/tracel-client/src/console/job/mod.rs index b5ea954..824d287 100644 --- a/tracel-client/src/console/job/mod.rs +++ b/tracel-client/src/console/job/mod.rs @@ -1,11 +1,22 @@ +pub mod response; + mod body; use crate::{ ClientError, - console::{Client, job::body::ComputeProviderQueueJobRequest}, + console::{ + Client, + job::{ + body::ComputeProviderQueueJobRequest, + response::{JobLogResponse, JobResponse, ProjectJobsResponse, QueuedJobResponse}, + }, + }, }; impl Client { + /// Queue a job that runs a code version on a compute provider group. + /// + /// The client must be logged in before calling this method. pub fn start_remote_job( &self, compute_provider_group_name: &str, @@ -13,16 +24,78 @@ impl Client { project_name: &str, digest: &str, command: &str, - ) -> Result<(), ClientError> { - let path: &str = &format!("projects/{owner_name}/{project_name}/jobs/queue"); - let url = self.transport.join(path); - + ) -> Result { let body = ComputeProviderQueueJobRequest { compute_provider_group_name: compute_provider_group_name.to_string(), digest: digest.to_string(), command: command.to_string(), }; - self.transport.post(url, Some(body)) + self.transport.post_json( + format!("projects/{owner_name}/{project_name}/jobs/queue"), + Some(body), + ) + } + + /// List the jobs of a project. + /// + /// The client must be logged in before calling this method. + pub fn list_jobs( + &self, + namespace: &str, + project_name: &str, + ) -> Result { + self.transport + .get_json(format!("projects/{namespace}/{project_name}/jobs")) + } + + /// Get a job by its project-scoped number. + /// + /// The client must be logged in before calling this method. + pub fn get_job( + &self, + namespace: &str, + project_name: &str, + job_num: i32, + ) -> Result { + self.transport.get_json(format!( + "projects/{namespace}/{project_name}/jobs/{job_num}" + )) + } + + /// Read a page of a job's log file from byte offset `start`, or from the beginning. + /// + /// The client must be logged in before calling this method. + pub fn get_job_logs( + &self, + namespace: &str, + project_name: &str, + job_num: i32, + start: Option, + ) -> Result { + let mut url = self.transport.join(&format!( + "projects/{namespace}/{project_name}/jobs/{job_num}/logs" + )); + if let Some(start) = start { + url.query_pairs_mut() + .append_pair("start", &start.to_string()); + } + + self.transport.get_json(url) + } + + /// Cancel a new, queued or running job. + /// + /// The client must be logged in before calling this method. + pub fn cancel_job( + &self, + namespace: &str, + project_name: &str, + job_num: i32, + ) -> Result<(), ClientError> { + self.transport.put( + format!("projects/{namespace}/{project_name}/jobs/{job_num}/cancel"), + None::<()>, + ) } } diff --git a/tracel-client/src/console/job/response.rs b/tracel-client/src/console/job/response.rs new file mode 100644 index 0000000..4f2c211 --- /dev/null +++ b/tracel-client/src/console/job/response.rs @@ -0,0 +1,87 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct QueuedJobResponse { + pub job_id: String, + pub compute_provider_group_id: String, + /// The project-scoped job number, or `None` from a server that does not send it. + #[serde(default)] + pub job_num: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ProjectJobsResponse { + pub jobs: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ProjectJobResponse { + pub job_num: i32, + pub job_project_version: String, + pub job_command: String, + /// `new`, `queued`, `running`, `pending_cancellation`, `completed`, `failed` or `cancelled`. + pub status: String, + pub execution_context: Option, + pub created_at: String, + pub started_at: Option, + pub completed_at: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct JobResponse { + pub job_num: i32, + pub compute_provider_id: Option, + pub job_project: JobProjectResponse, + pub job_project_version: String, + pub job_command: String, + /// `new`, `queued`, `running`, `pending_cancellation`, `completed`, `failed` or `cancelled`. + pub status: String, + pub status_message: String, + pub execution_context: Option, + pub created_at: String, + pub started_at: Option, + pub completed_at: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct JobProjectResponse { + pub project_name: String, + pub namespace_name: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "type")] +pub enum ExecutionContextResponse { + Managed { + compute_provider_name: String, + memory_gb: i32, + cpu_cores: i32, + num_gpus: Option, + lock_price_per_hour: MoneyResponse, + final_cost: Option, + estimated_cost: Option, + }, + SelfManaged { + compute_provider_name: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MoneyResponse { + /// A decimal amount. + pub amount: String, + pub currency: String, +} + +/// One page of a job's log file. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct JobLogResponse { + pub logs: String, + /// Byte offset of the first byte in `logs`. + pub start: u64, + /// Byte offset after the last byte in `logs`; pass it as the next `start`. + pub end: u64, + /// Size of the log file in bytes. + pub total_size: u64, + pub has_more: bool, +} diff --git a/tracel-client/src/error.rs b/tracel-client/src/error.rs index 9f454f1..5c59d8a 100644 --- a/tracel-client/src/error.rs +++ b/tracel-client/src/error.rs @@ -23,6 +23,7 @@ pub enum ApiErrorCode { ModelVersionUploadIncomplete, MultipartUploadIncomplete, ModelVersionConflict, + ComputeProviderJob, Unauthenticated, CredentialNotAllowed, // ... diff --git a/tracel-client/src/transport.rs b/tracel-client/src/transport.rs index ff5e3c4..084f557 100644 --- a/tracel-client/src/transport.rs +++ b/tracel-client/src/transport.rs @@ -236,6 +236,13 @@ impl ApiTransport { Ok(serde_json::from_slice::(&bytes)?) } + pub fn put(&self, path: impl AsRef, body: Option) -> Result<(), ClientError> + where + T: serde::Serialize, + { + self.req(reqwest::Method::PUT, path, body).map(|_| ()) + } + pub fn delete(&self, path: impl AsRef) -> Result<(), ClientError> { self.req(reqwest::Method::DELETE, path, None::) .map(|_| ()) From b1f87bd22bcbb3aa0c4243e9ef31f4caa9716b1a Mon Sep 17 00:00:00 2001 From: Jonathan Richard Date: Tue, 6 Oct 2026 21:34:12 -0400 Subject: [PATCH 2/4] refactor: queue_job takes the project first like its siblings - `start_remote_job` becomes `queue_job(namespace, project_name, compute_provider_group_name, digest, command)`, matching the route and the argument order of every other project method. --- tracel-client/src/console/job/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tracel-client/src/console/job/mod.rs b/tracel-client/src/console/job/mod.rs index 824d287..1a1dfc2 100644 --- a/tracel-client/src/console/job/mod.rs +++ b/tracel-client/src/console/job/mod.rs @@ -14,14 +14,17 @@ use crate::{ }; impl Client { - /// Queue a job that runs a code version on a compute provider group. + /// Queue a job that runs the code version `digest` on a compute provider group. + /// + /// `command` holds the program's arguments, which the compute provider splits into + /// words with shell quoting rules. /// /// The client must be logged in before calling this method. - pub fn start_remote_job( + pub fn queue_job( &self, - compute_provider_group_name: &str, - owner_name: &str, + namespace: &str, project_name: &str, + compute_provider_group_name: &str, digest: &str, command: &str, ) -> Result { @@ -32,7 +35,7 @@ impl Client { }; self.transport.post_json( - format!("projects/{owner_name}/{project_name}/jobs/queue"), + format!("projects/{namespace}/{project_name}/jobs/queue"), Some(body), ) } From f8b0a246c742bdc86b802ea679f2e8ad2dd446e4 Mon Sep 17 00:00:00 2001 From: Jonathan Richard Date: Wed, 7 Oct 2026 00:00:05 -0400 Subject: [PATCH 3/4] chore: declare the Rust version the client needs - `rust-version` is 1.97.0, the lowest toolchain that builds and tests the workspace; the client itself uses `File::lock`, stable since 1.89. --- Cargo.toml | 1 + tracel-client/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a67d7d7..b4456c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ authors = ["Tracel team"] homepage = "https://burn.dev" repository = "https://github.com/tracel-ai/burn-central-client" license = "MIT OR Apache-2.0" +rust-version = "1.97.0" [workspace.dependencies] reqwest = { version = "0.13.4", features = ["blocking", "form"] } diff --git a/tracel-client/Cargo.toml b/tracel-client/Cargo.toml index 88a5ceb..9c3e6e5 100644 --- a/tracel-client/Cargo.toml +++ b/tracel-client/Cargo.toml @@ -2,6 +2,7 @@ name = "tracel-client" version.workspace = true edition.workspace = true +rust-version.workspace = true authors.workspace = true description = "Collection of HTTP calls required to communicate with the Tracel backend" homepage.workspace = true From 2a84a489814a058a6f2acf91b5b9c1118c10bc16 Mon Sep 17 00:00:00 2001 From: Jonathan Richard Date: Tue, 13 Oct 2026 22:12:22 -0400 Subject: [PATCH 4/4] feat: a missing job log is reported as such --- tracel-client/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tracel-client/src/error.rs b/tracel-client/src/error.rs index 5c59d8a..a72335f 100644 --- a/tracel-client/src/error.rs +++ b/tracel-client/src/error.rs @@ -24,6 +24,7 @@ pub enum ApiErrorCode { MultipartUploadIncomplete, ModelVersionConflict, ComputeProviderJob, + JobLog, Unauthenticated, CredentialNotAllowed, // ...