open_ai: Make Assistant message content optional (#31418)

Ben Brandt created

Fixes regression caused by:
https://github.com/zed-industries/zed/pull/30639

Assistant messages can come back with no content, and we no longer
allowed that in the deserialization.

Release Notes:

- open_ai: fixed deserialization issue if assistant content was empty

Change summary

crates/language_models/src/provider/open_ai.rs | 12 +++++++++---
crates/open_ai/src/open_ai.rs                  | 15 ++++++++-------
2 files changed, 17 insertions(+), 10 deletions(-)

Detailed changes

crates/language_models/src/provider/open_ai.rs 🔗

@@ -400,7 +400,7 @@ pub fn into_open_ai(
                         tool_calls.push(tool_call);
                     } else {
                         messages.push(open_ai::RequestMessage::Assistant {
-                            content: open_ai::MessageContent::empty(),
+                            content: None,
                             tool_calls: vec![tool_call],
                         });
                     }
@@ -474,7 +474,13 @@ fn add_message_content_part(
 ) {
     match (role, messages.last_mut()) {
         (Role::User, Some(open_ai::RequestMessage::User { content }))
-        | (Role::Assistant, Some(open_ai::RequestMessage::Assistant { content, .. }))
+        | (
+            Role::Assistant,
+            Some(open_ai::RequestMessage::Assistant {
+                content: Some(content),
+                ..
+            }),
+        )
         | (Role::System, Some(open_ai::RequestMessage::System { content, .. })) => {
             content.push_part(new_part);
         }
@@ -484,7 +490,7 @@ fn add_message_content_part(
                     content: open_ai::MessageContent::from(vec![new_part]),
                 },
                 Role::Assistant => open_ai::RequestMessage::Assistant {
-                    content: open_ai::MessageContent::from(vec![new_part]),
+                    content: Some(open_ai::MessageContent::from(vec![new_part])),
                     tool_calls: Vec::new(),
                 },
                 Role::System => open_ai::RequestMessage::System {

crates/open_ai/src/open_ai.rs 🔗

@@ -278,7 +278,7 @@ pub struct FunctionDefinition {
 #[serde(tag = "role", rename_all = "lowercase")]
 pub enum RequestMessage {
     Assistant {
-        content: MessageContent,
+        content: Option<MessageContent>,
         #[serde(default, skip_serializing_if = "Vec::is_empty")]
         tool_calls: Vec<ToolCall>,
     },
@@ -562,16 +562,16 @@ fn adapt_response_to_stream(response: Response) -> ResponseStreamEvent {
             .into_iter()
             .map(|choice| {
                 let content = match &choice.message {
-                    RequestMessage::Assistant { content, .. } => content,
-                    RequestMessage::User { content } => content,
-                    RequestMessage::System { content } => content,
-                    RequestMessage::Tool { content, .. } => content,
+                    RequestMessage::Assistant { content, .. } => content.as_ref(),
+                    RequestMessage::User { content } => Some(content),
+                    RequestMessage::System { content } => Some(content),
+                    RequestMessage::Tool { content, .. } => Some(content),
                 };
 
                 let mut text_content = String::new();
                 match content {
-                    MessageContent::Plain(text) => text_content.push_str(&text),
-                    MessageContent::Multipart(parts) => {
+                    Some(MessageContent::Plain(text)) => text_content.push_str(&text),
+                    Some(MessageContent::Multipart(parts)) => {
                         for part in parts {
                             match part {
                                 MessagePart::Text { text } => text_content.push_str(&text),
@@ -579,6 +579,7 @@ fn adapt_response_to_stream(response: Response) -> ResponseStreamEvent {
                             }
                         }
                     }
+                    None => {}
                 };
 
                 ChoiceDelta {