patch api wip

This commit is contained in:
Brandon Thomas committed 2025-12-16 22:11:57 -05:00
1 parent 0fa2b0a4c9
commit d31ba92d43
3 files changed
+326 -2

No files matched your search

@@ -25,8 +25,126 @@ fetch("https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects/6623842d-3669-49b
"credentials": "include"
});
Request Payload:
{"object":{"metadata":{"version":"0.0.1","createdAt":1765433415549,"updatedAt":1765433417487,"useAdvancedEditing":false,"draftMode":false,"nodes":{"9b864e10-5a9b-46a1-8a94-2b571649a60d":{"id":"9b864e10-5a9b-46a1-8a94-2b571649a60d","type":"input","parentId":null,"source":{"type":"image","image_url":"https://cdn.marble.worldlabs.ai/object/ee33d03e-b153-4d56-a7cb-a08bf86bd20e/asset.jpg"},"createdAt":1765433417487},"22b9587b-2b4d-49c2-a6eb-e985d890e7db":{"id":"22b9587b-2b4d-49c2-a6eb-e985d890e7db","type":"pano","parentId":"9b864e10-5a9b-46a1-8a94-2b571649a60d","source":{"type":"generate_world","world_id":"pending","status":"pending"},"createdAt":1765433417487},"8e63a168-0c85-470d-b655-b185e0e13fee":{"id":"8e63a168-0c85-470d-b655-b185e0e13fee","type":"world","parentId":"22b9587b-2b4d-49c2-a6eb-e985d890e7db","source":{"type":"generate_world","world_id":"pending","posed_cubemaps_url":"","minimap_url":"","minimap_metadata":"","status":"pending"},"createdAt":1765433417487}}}},"update_mask":["metadata"]}
Request Payload Pretty Print:
{
"object": {
"metadata": {
"version": "0.0.1",
"createdAt": 1765433415549,
"updatedAt": 1765433417487,
"useAdvancedEditing": false,
"draftMode": false,
"nodes": {
"9b864e10-5a9b-46a1-8a94-2b571649a60d": {
"id": "9b864e10-5a9b-46a1-8a94-2b571649a60d",
"type": "input",
"parentId": null,
"source": {
"type": "image",
"image_url": "https://cdn.marble.worldlabs.ai/object/ee33d03e-b153-4d56-a7cb-a08bf86bd20e/asset.jpg"
},
"createdAt": 1765433417487
},
"22b9587b-2b4d-49c2-a6eb-e985d890e7db": {
"id": "22b9587b-2b4d-49c2-a6eb-e985d890e7db",
"type": "pano",
"parentId": "9b864e10-5a9b-46a1-8a94-2b571649a60d",
"source": {
"type": "generate_world",
"world_id": "pending",
"status": "pending"
},
"createdAt": 1765433417487
},
"8e63a168-0c85-470d-b655-b185e0e13fee": {
"id": "8e63a168-0c85-470d-b655-b185e0e13fee",
"type": "world",
"parentId": "22b9587b-2b4d-49c2-a6eb-e985d890e7db",
"source": {
"type": "generate_world",
"world_id": "pending",
"posed_cubemaps_url": "",
"minimap_url": "",
"minimap_metadata": "",
"status": "pending"
},
"createdAt": 1765433417487
}
}
}
},
"update_mask": [
"metadata"
]
}
Response:
{"context":null,"id":"6623842d-3669-49b9-976f-5b239c24e353","display_name":null,"owner_id":"hdYte3rVrWbswJZEglL5CdmKfxv1","status":"CREATED","created_at":1765433415,"updated_at":1765433417,"object_uri":null,"mime_type":"application/run+json","metadata":{"version":"0.0.1","createdAt":1765433415549,"updatedAt":1765433417487,"useAdvancedEditing":false,"draftMode":false,"nodes":{"9b864e10-5a9b-46a1-8a94-2b571649a60d":{"id":"9b864e10-5a9b-46a1-8a94-2b571649a60d","type":"input","parentId":null,"source":{"type":"image","image_url":"https://cdn.marble.worldlabs.ai/object/ee33d03e-b153-4d56-a7cb-a08bf86bd20e/asset.jpg"},"createdAt":1765433417487},"22b9587b-2b4d-49c2-a6eb-e985d890e7db":{"id":"22b9587b-2b4d-49c2-a6eb-e985d890e7db","type":"pano","parentId":"9b864e10-5a9b-46a1-8a94-2b571649a60d","source":{"type":"generate_world","world_id":"pending","status":"pending"},"createdAt":1765433417487},"8e63a168-0c85-470d-b655-b185e0e13fee":{"id":"8e63a168-0c85-470d-b655-b185e0e13fee","type":"world","parentId":"22b9587b-2b4d-49c2-a6eb-e985d890e7db","source":{"type":"generate_world","world_id":"pending","posed_cubemaps_url":"","minimap_url":"","minimap_metadata":"","status":"pending"},"createdAt":1765433417487}}}}
Response Pretty Print:
{
"context": null,
"id": "6623842d-3669-49b9-976f-5b239c24e353",
"display_name": null,
"owner_id": "hdYte3rVrWbswJZEglL5CdmKfxv1",
"status": "CREATED",
"created_at": 1765433415,
"updated_at": 1765433417,
"object_uri": null,
"mime_type": "application/run+json",
"metadata": {
"version": "0.0.1",
"createdAt": 1765433415549,
"updatedAt": 1765433417487,
"useAdvancedEditing": false,
"draftMode": false,
"nodes": {
"9b864e10-5a9b-46a1-8a94-2b571649a60d": {
"id": "9b864e10-5a9b-46a1-8a94-2b571649a60d",
"type": "input",
"parentId": null,
"source": {
"type": "image",
"image_url": "https://cdn.marble.worldlabs.ai/object/ee33d03e-b153-4d56-a7cb-a08bf86bd20e/asset.jpg"
},
"createdAt": 1765433417487
},
"22b9587b-2b4d-49c2-a6eb-e985d890e7db": {
"id": "22b9587b-2b4d-49c2-a6eb-e985d890e7db",
"type": "pano",
"parentId": "9b864e10-5a9b-46a1-8a94-2b571649a60d",
"source": {
"type": "generate_world",
"world_id": "pending",
"status": "pending"
},
"createdAt": 1765433417487
},
"8e63a168-0c85-470d-b655-b185e0e13fee": {
"id": "8e63a168-0c85-470d-b655-b185e0e13fee",
"type": "world",
"parentId": "22b9587b-2b4d-49c2-a6eb-e985d890e7db",
"source": {
"type": "generate_world",
"world_id": "pending",
"posed_cubemaps_url": "",
"minimap_url": "",
"minimap_metadata": "",
"status": "pending"
},
"createdAt": 1765433417487
}
}
}
}
The URL is accessible in the browser:
https://cdn.marble.worldlabs.ai/object/ee33d03e-b153-4d56-a7cb-a08bf86bd20e/asset.jpg
@@ -1,4 +1,5 @@
pub mod create_object;
pub mod begin_object_image_attachment;
pub mod begin_object_image_upload;
pub mod finalize_object_image_upload;
pub mod create_object;
pub mod finalize_object_image_upload;
pub mod patch_object_run_after_upload;
@@ -0,0 +1,205 @@
use crate::api::api_types::object_id::ObjectId;
use crate::api::api_types::upload_id::UploadId;
use crate::api::api_types::upload_mime_type::UploadMimeType;
use crate::api::api_types::world_id::WorldId;
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
use crate::api::utils::upload_id_to_image_url::upload_id_to_image_url;
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
use crate::credentials::world_labs_cookies::WorldLabsCookies;
use crate::error::world_labs_client_error::WorldLabsClientError;
use crate::error::world_labs_error::WorldLabsError;
use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError;
use http_headers::names::{PRIORITY, SEC_FETCH_DEST, SEC_FETCH_MODE, SEC_FETCH_SITE, SEC_GPC};
use http_headers::values::accept::ACCEPT_ALL;
use http_headers::values::cache_control::CACHE_CONTROL_NO_CACHE;
use http_headers::values::connection::CONNECTION_KEEP_ALIVE;
use http_headers::values::content_type::CONTENT_TYPE_APPLICATION_JSON;
use http_headers::values::pragma::PRAGMA_NO_CACHE;
use http_headers::values::priority::PRIORITY_4;
use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE};
use http_headers::values::te::TE_TRAILERS;
use log::{debug, error};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, ORIGIN, PRAGMA, REFERER, TE};
use wreq::Client;
use wreq_util::Emulation;
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
fn get_url(run_id: &ObjectId) -> String {
format!("{}/{}", BASE_URL, run_id.0)
}
pub struct PatchObjectRunAfterUploadArgs<'a> {
pub cookies: &'a WorldLabsCookies,
pub bearer_token: &'a WorldLabsBearerToken,
pub run_id: &'a ObjectId,
pub upload_id: &'a UploadId,
pub upload_mime_type: UploadMimeType,
pub text_prompt: &'a str,
pub request_timeout: Option<Duration>,
}
pub struct PatchObjectRunAfterUploadResponse {
pub world_id: WorldId,
}
/// Marble Image-to-World
/// This request patches an object. Seems to update it with the attached files.
/// Request #6 (of ~10)
pub async fn patch_object_run_after_upload(args: PatchObjectRunAfterUploadArgs<'_>) -> Result<PatchObjectRunAfterUploadResponse, WorldLabsError> {
let client = Client::builder()
.emulation(Emulation::Firefox143)
.build()
.map_err(|err| WorldLabsClientError::WreqClientError(err))?;
let url = get_url(args.run_id);
debug!("Requesting URL: {}", url);
let mut request_builder = client.patch(url)
.header(ACCEPT, ACCEPT_ALL)
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_ENCODING, "gzip, deflate, br, zstd")
.header(REFERER, REFERER_VALUE)
.header(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON)
.header(AUTHORIZATION, args.bearer_token.to_bearer_token_string())
.header(ORIGIN, ORIGIN_VALUE)
.header(SEC_GPC, "1")
.header(CONNECTION, CONNECTION_KEEP_ALIVE)
.header(SEC_FETCH_DEST, SEC_FETCH_DEST_EMPTY)
.header(SEC_FETCH_MODE, SEC_FETCH_MODE_CORS)
.header(SEC_FETCH_SITE, SEC_FETCH_SITE_CROSS_SITE)
.header(PRIORITY, PRIORITY_4)
.header(PRAGMA, PRAGMA_NO_CACHE)
.header(CACHE_CONTROL, CACHE_CONTROL_NO_CACHE)
.header(TE, TE_TRAILERS);
if let Some(timeout) = args.request_timeout {
request_builder = request_builder.timeout(timeout);
}
let request_payload = RawRequest::for_image_and_run(args.upload_id, args.upload_mime_type, args.run_id, args.text_prompt);
let http_request = request_builder.json(&request_payload)
.build()
.map_err(|err| {
error!("Error building request: {:?}", err);
WorldLabsClientError::WreqClientError(err)
})?;
let response = client.execute(http_request)
.await
.map_err(|err| {
error!("Error during request execution: {:?}", err);
WorldLabsGenericApiError::WreqError(err)
})?;
let status = response.status();
let response_body = response.text()
.await
.map_err(|err| {
error!("Error reading response body: {:?}", err);
WorldLabsGenericApiError::WreqError(err)
})?;
// TODO: Handle errors (Cloudflare, Grok, etc.)
if !status.is_success() {
error!("Request returned an error (code {}) : {:?}", status.as_u16(), response_body);
//return Err(classify_general_http_status_code_and_body(status, response_body));
return Err(WorldLabsGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code: status, body: response_body}.into())
}
debug!("Response body (200): {}", response_body);
let response : RawResponse = serde_json::from_str(&response_body)
.map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
Ok(PatchObjectRunAfterUploadResponse {
world_id: WorldId(response.id),
})
}
#[derive(Serialize)]
struct RawRequest {
pub object: ObjectDef,
pub generation_input: GenerationInput,
pub context: Context,
}
#[derive(Serialize)]
struct ObjectDef {
pub metadata: ObjectMetadata,
}
#[derive(Serialize)]
struct ObjectMetadata {
pub version: String,
#[serde(rename = "createdAt")]
pub created_at: String,
#[serde(rename = "updatedAt")]
pub updated_at: String,
#[serde(rename = "usesAdvancedEditing")]
pub uses_advanced_editing: bool,
#[serde(rename = "draftMode")]
pub draft_mode: bool,
}
#[derive(Serialize)]
struct GenerationInput {
pub model: String,
pub prompt: Prompt,
}
#[derive(Serialize)]
struct Prompt {
pub r#type: String,
pub image_prompt: ImagePrompt,
pub text_prompt: String,
}
#[derive(Serialize)]
struct ImagePrompt {
pub uri: String,
}
#[derive(Serialize)]
struct Context {
pub run_id: String,
}
impl RawRequest {
pub fn for_image_and_run(image_upload_id: &UploadId, image_mime_type: UploadMimeType, run_id: &ObjectId, text_prompt: &str) -> Self {
let image_url = upload_id_to_image_url(image_upload_id, image_mime_type);
Self {
permission: Permission {
public: true,
},
generation_input: GenerationInput {
model: "Marble 0.1-plus".to_string(),
prompt: Prompt {
r#type: "image".to_string(),
image_prompt: ImagePrompt {
uri: image_url,
},
text_prompt: text_prompt.to_string(),
},
},
context: Context {
run_id: run_id.0.clone(),
}
}
}
}
#[derive(Deserialize)]
struct RawResponse {
pub id: String,
}
#[cfg(test)]
mod tests {
}