diff --git a/src/mitm/modify.rs b/src/mitm/modify.rs index f78158e..2cf3191 100644 --- a/src/mitm/modify.rs +++ b/src/mitm/modify.rs @@ -85,7 +85,17 @@ pub fn modify_request(body: &[u8], tool_ctx: Option<&ToolContext>) -> Option") { @@ -151,8 +161,16 @@ pub fn modify_request(body: &[u8], tool_ctx: Option<&ToolContext>) -> Option Vec { build_request_with_image(cascade_id, text, oauth_token, model_enum, None) } -/// Image data to embed in the ChatMessage protobuf. +/// Image data to attach to a cascade message. pub struct ImageData { /// MIME type, e.g. "image/png", "image/jpeg", "image/webp", "image/gif" pub mime_type: String, - /// Raw image bytes (NOT base64 — already decoded) + /// Raw image bytes (NOT base64 — we base64-encode them into the proto) pub data: Vec, } /// Build `SendUserCascadeMessageRequest` with optional image attachment. /// -/// When `image` is Some, the ChatMessage includes a Blob field (field 6) -/// alongside the text (field 1). This matches how the real Antigravity -/// webview sends images: `ChatMessage { text, blob: { mime_type, data } }`. +/// Field layout (from JS protobuf-es registration in workbench bundle): +/// 1: cascade_id (string) +/// 2: items (ChatMessage, repeated) — contains the text +/// 3: metadata (auto-populated by LS) +/// 4: experiment_config +/// 5: cascade_config (PlannerConfig) +/// 6: images (ImageData, repeated) — TOP-LEVEL, NOT inside ChatMessage +/// 8: blocking (bool) +/// 9: additional_steps (repeated) +/// 10: artifact_comments (repeated) +/// 11: client_type (enum) +/// 12: file_diff_comments (repeated) +/// 13: file_comments (repeated) +/// 14: media (repeated) +/// 15: agent_script_item +/// 16: propagate_error (bool) +/// 17: planner_response +/// +/// ImageData proto (from codeium_common_pb): +/// 1: base64_data (string) — BASE64 encoded, NOT raw bytes +/// 2: mime_type (string) +/// 3: caption (string) +/// 4: uri (string) pub fn build_request_with_image( cascade_id: &str, text: &str, @@ -151,13 +170,8 @@ pub fn build_request_with_image( // Field 1: cascade_id msg.extend(proto_string(1, cascade_id.as_bytes())); - // Field 2: ChatMessage { f1: text, f6?: Blob { f1: mime_type, f2: data } } - let mut chat_msg = proto_string(1, text.as_bytes()); - if let Some(img) = image { - let mut blob = proto_string(1, img.mime_type.as_bytes()); - blob.extend(proto_string(2, &img.data)); - chat_msg.extend(proto_message(6, &blob)); - } + // Field 2: items (ChatMessage) — text only, no blob + let chat_msg = proto_string(1, text.as_bytes()); msg.extend(proto_message(2, &chat_msg)); // Field 3: Metadata (Auth + Client ID) @@ -169,7 +183,7 @@ pub fn build_request_with_image( meta.extend(proto_string(12, CLIENT_NAME.as_bytes())); msg.extend(proto_message(3, &meta)); - // Field 5: PlannerConfig + // Field 5: PlannerConfig (cascade_config) let mut inner = Vec::new(); // field 2: conversational mode { f4: 1, f14: 0 } @@ -204,7 +218,17 @@ pub fn build_request_with_image( .concat(); msg.extend(proto_message(5, &f5_payload)); - // Field 11: conversation history flag + // Field 6: images (ImageData, repeated) — TOP-LEVEL field + // ImageData { 1: base64_data (string), 2: mime_type (string) } + if let Some(img) = image { + use base64::Engine; + let b64 = base64::engine::general_purpose::STANDARD.encode(&img.data); + let mut image_data = proto_string(1, b64.as_bytes()); + image_data.extend(proto_string(2, img.mime_type.as_bytes())); + msg.extend(proto_message(6, &image_data)); + } + + // Field 11: client_type (but we use it for conversation history flag) msg.extend(bool_field(11, true)); msg