fix(gemini): resolve 400 stream errors and improve client compatibility
- Filter out empty text parts in Gemini requests to avoid 400 errors. - Inject 'assistant' role into the first streaming chunk for better compatibility with clients like opencode. - Fallback to tool_call_id for Gemini function responses when name is missing.
This commit is contained in:
@@ -200,12 +200,14 @@ impl GeminiProvider {
|
|||||||
if msg.role == "system" {
|
if msg.role == "system" {
|
||||||
for part in msg.content {
|
for part in msg.content {
|
||||||
if let ContentPart::Text { text } = part {
|
if let ContentPart::Text { text } = part {
|
||||||
system_parts.push(GeminiPart {
|
if !text.trim().is_empty() {
|
||||||
text: Some(text),
|
system_parts.push(GeminiPart {
|
||||||
inline_data: None,
|
text: Some(text),
|
||||||
function_call: None,
|
inline_data: None,
|
||||||
function_response: None,
|
function_call: None,
|
||||||
});
|
function_response: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -230,7 +232,8 @@ impl GeminiProvider {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let name = msg.name.clone().unwrap_or_default();
|
// Gemini function response MUST have a name. Fallback to tool_call_id if name is missing.
|
||||||
|
let name = msg.name.clone().or_else(|| msg.tool_call_id.clone()).unwrap_or_else(|| "unknown_function".to_string());
|
||||||
let response_value = serde_json::from_str::<Value>(&text_content)
|
let response_value = serde_json::from_str::<Value>(&text_content)
|
||||||
.unwrap_or_else(|_| serde_json::json!({ "result": text_content }));
|
.unwrap_or_else(|_| serde_json::json!({ "result": text_content }));
|
||||||
|
|
||||||
@@ -249,7 +252,7 @@ impl GeminiProvider {
|
|||||||
// Include text content if present
|
// Include text content if present
|
||||||
for p in &msg.content {
|
for p in &msg.content {
|
||||||
if let ContentPart::Text { text } = p {
|
if let ContentPart::Text { text } = p {
|
||||||
if !text.is_empty() {
|
if !text.trim().is_empty() {
|
||||||
parts.push(GeminiPart {
|
parts.push(GeminiPart {
|
||||||
text: Some(text.clone()),
|
text: Some(text.clone()),
|
||||||
inline_data: None,
|
inline_data: None,
|
||||||
@@ -279,12 +282,14 @@ impl GeminiProvider {
|
|||||||
for part in msg.content {
|
for part in msg.content {
|
||||||
match part {
|
match part {
|
||||||
ContentPart::Text { text } => {
|
ContentPart::Text { text } => {
|
||||||
parts.push(GeminiPart {
|
if !text.trim().is_empty() {
|
||||||
text: Some(text),
|
parts.push(GeminiPart {
|
||||||
inline_data: None,
|
text: Some(text),
|
||||||
function_call: None,
|
inline_data: None,
|
||||||
function_response: None,
|
function_call: None,
|
||||||
});
|
function_response: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ContentPart::Image(image_input) => {
|
ContentPart::Image(image_input) => {
|
||||||
let (base64_data, mime_type) = image_input
|
let (base64_data, mime_type) = image_input
|
||||||
|
|||||||
@@ -250,10 +250,18 @@ async fn chat_completions(
|
|||||||
// Build stream that yields events wrapped in Result
|
// Build stream that yields events wrapped in Result
|
||||||
let stream = async_stream::stream! {
|
let stream = async_stream::stream! {
|
||||||
let mut aggregator = Box::pin(aggregating_stream);
|
let mut aggregator = Box::pin(aggregating_stream);
|
||||||
|
let mut first_chunk = true;
|
||||||
|
|
||||||
while let Some(chunk_result) = aggregator.next().await {
|
while let Some(chunk_result) = aggregator.next().await {
|
||||||
match chunk_result {
|
match chunk_result {
|
||||||
Ok(chunk) => {
|
Ok(chunk) => {
|
||||||
|
let role = if first_chunk {
|
||||||
|
first_chunk = false;
|
||||||
|
Some("assistant".to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let response = ChatCompletionStreamResponse {
|
let response = ChatCompletionStreamResponse {
|
||||||
id: stream_id_sse.clone(),
|
id: stream_id_sse.clone(),
|
||||||
object: "chat.completion.chunk".to_string(),
|
object: "chat.completion.chunk".to_string(),
|
||||||
@@ -262,7 +270,7 @@ async fn chat_completions(
|
|||||||
choices: vec![ChatStreamChoice {
|
choices: vec![ChatStreamChoice {
|
||||||
index: 0,
|
index: 0,
|
||||||
delta: ChatStreamDelta {
|
delta: ChatStreamDelta {
|
||||||
role: None,
|
role,
|
||||||
content: Some(chunk.content),
|
content: Some(chunk.content),
|
||||||
reasoning_content: chunk.reasoning_content,
|
reasoning_content: chunk.reasoning_content,
|
||||||
tool_calls: chunk.tool_calls,
|
tool_calls: chunk.tool_calls,
|
||||||
|
|||||||
Reference in New Issue
Block a user