支持gemini cli 的 Agent 解析用户消息里的图片

This commit is contained in:
xintaofei
2026-03-08 17:46:01 +08:00
parent 533064829f
commit c5537f63b0

View File

@@ -154,6 +154,109 @@ impl GeminiParser {
.or_else(|| message.get("message").and_then(Self::extract_text))
}
fn parse_data_uri_image(raw: &str) -> Option<(String, String)> {
let trimmed = raw.trim();
let without_prefix = trimmed.strip_prefix("data:")?;
let marker = ";base64,";
let marker_idx = without_prefix.find(marker)?;
let mime_type = without_prefix.get(..marker_idx)?.trim();
if !mime_type.starts_with("image/") {
return None;
}
let data = without_prefix.get(marker_idx + marker.len()..)?.trim();
if data.is_empty() {
return None;
}
Some((mime_type.to_string(), data.to_string()))
}
fn parse_user_image_part(part: &serde_json::Value) -> Option<ContentBlock> {
let inline = part
.get("inlineData")
.or_else(|| part.get("inline_data"))
.unwrap_or(part);
let data = inline
.get("data")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|v| !v.is_empty())?;
if let Some((mime_type, data)) = Self::parse_data_uri_image(data) {
return Some(ContentBlock::Image {
data,
mime_type,
uri: None,
});
}
let mime_type = inline
.get("mimeType")
.or_else(|| inline.get("mime_type"))
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|m| !m.is_empty() && m.starts_with("image/"))?;
let uri = inline
.get("fileUri")
.or_else(|| inline.get("uri"))
.and_then(|u| u.as_str())
.map(|s| s.to_string());
Some(ContentBlock::Image {
data: data.to_string(),
mime_type: mime_type.to_string(),
uri,
})
}
fn parse_user_blocks(message: &serde_json::Value) -> Vec<ContentBlock> {
let mut blocks = Vec::new();
let content = match message.get("content") {
Some(c) => c,
None => {
if let Some(text) = message.get("message").and_then(Self::extract_text) {
blocks.push(ContentBlock::Text { text });
}
return blocks;
}
};
if let Some(text) = content
.as_str()
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
{
blocks.push(ContentBlock::Text { text });
return blocks;
}
if let Some(parts) = content.as_array() {
for part in parts {
if let Some(text) = part.get("text").and_then(Self::extract_text) {
blocks.push(ContentBlock::Text { text });
} else if let Some(text) = Self::extract_text(part) {
blocks.push(ContentBlock::Text { text });
}
if let Some(image) = Self::parse_user_image_part(part) {
blocks.push(image);
}
}
return blocks;
}
if let Some(image) = Self::parse_user_image_part(content) {
blocks.push(image);
return blocks;
}
if let Some(text) = Self::extract_text(content) {
blocks.push(ContentBlock::Text { text });
}
blocks
}
fn parse_summary_from_value(
&self,
path: &Path,
@@ -382,13 +485,14 @@ impl GeminiParser {
match msg_type.as_str() {
"user" => {
let Some(text) = Self::extract_message_text(&raw) else {
let blocks = Self::parse_user_blocks(&raw);
if blocks.is_empty() {
continue;
};
}
messages.push(UnifiedMessage {
id: msg_id,
role: MessageRole::User,
content: vec![ContentBlock::Text { text }],
content: blocks,
timestamp,
usage: None,
duration_ms: None,
@@ -609,6 +713,7 @@ fn group_into_turns(messages: Vec<UnifiedMessage>) -> Vec<MessageTurn> {
mod tests {
use super::resolve_gemini_base_dir_from;
use super::GeminiParser;
use crate::models::ContentBlock;
use crate::parsers::AgentParser;
use std::env;
use std::fs;
@@ -706,4 +811,44 @@ mod tests {
let resolved = resolve_gemini_base_dir_from(None, Some(PathBuf::from("/Users/default")));
assert_eq!(resolved, PathBuf::from("/Users/default/.gemini"));
}
#[test]
fn parses_user_inline_image_block() {
let message = serde_json::json!({
"content": [
{"text": "这是什么"},
{"inlineData": {"mimeType": "image/jpeg", "data": "QUJD"}}
]
});
let blocks = GeminiParser::parse_user_blocks(&message);
assert_eq!(blocks.len(), 2);
assert!(matches!(&blocks[0], ContentBlock::Text { text } if text == "这是什么"));
assert!(matches!(
&blocks[1],
ContentBlock::Image { data, mime_type, uri }
if data == "QUJD" && mime_type == "image/jpeg" && uri.is_none()
));
}
#[test]
fn parses_user_data_uri_image_block() {
let message = serde_json::json!({
"content": [
{
"inlineData": {
"data": "data:image/png;base64,QUJD"
}
}
]
});
let blocks = GeminiParser::parse_user_blocks(&message);
assert_eq!(blocks.len(), 1);
assert!(matches!(
&blocks[0],
ContentBlock::Image { data, mime_type, uri }
if data == "QUJD" && mime_type == "image/png" && uri.is_none()
));
}
}