diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index 8a9c1e95ee4..42026eda297 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -1958,7 +1958,7 @@ impl NativeAgent { prompt_name: String, server_id: ContextServerId, arguments: HashMap, - original_content: Vec, + original_content: Vec, cx: &mut Context, ) -> Task> { let Some(state) = self.session_project_state(&session_id) else { @@ -1995,13 +1995,13 @@ impl NativeAgent { original_content.into_iter().skip(1), path_style, cx, - ); - }); + ) + })?; for message in prompt.messages { let context_server::types::PromptMessage { role, content } = message; let block = mcp_message_content_to_acp_content_block(content); - let display_block = acp_thread::content::from_v1(block.clone())?; + let display_block = block.clone(); match role { context_server::types::Role::User => { @@ -2017,8 +2017,8 @@ impl NativeAgent { }); thread.update(cx, |thread, cx| { - thread.push_acp_user_block(id, [block], path_style, cx); - }); + thread.push_acp_user_block(id, [block], path_style, cx) + })?; } context_server::types::Role::Assistant => { acp_thread.update(cx, |acp_thread, cx| { @@ -2030,9 +2030,7 @@ impl NativeAgent { ); }); - thread.update(cx, |thread, cx| { - thread.push_acp_agent_block(block, cx); - }); + thread.update(cx, |thread, cx| thread.push_acp_agent_block(block, cx))?; } } @@ -2114,7 +2112,7 @@ impl NativeAgent { client_user_message_id: ClientUserMessageId, session_id: acp_v2::SessionId, skill: Skill, - original_content: Vec, + original_content: Vec, cx: &mut Context, ) -> Task> { let Some(state) = self.session_project_state(&session_id) else { @@ -2158,10 +2156,10 @@ impl NativeAgent { })? }; let envelope = crate::tools::render_skill_envelope(&skill, &body); - let envelope_block = acp_v1::ContentBlock::Text(acp_v1::TextContent::new(envelope)); + let envelope_block = acp_v2::ContentBlock::Text(acp_v2::TextContent::new(envelope)); let mut user_blocks = original_content; - if let Some(acp_v1::ContentBlock::Text(text_content)) = user_blocks.first_mut() { + if let Some(acp_v2::ContentBlock::Text(text_content)) = user_blocks.first_mut() { let stripped = strip_slash_command_prefix(&text_content.text); if stripped.trim().is_empty() { user_blocks.remove(0); @@ -2175,7 +2173,7 @@ impl NativeAgent { // user's own typed message is already rendered by the normal // prompt flow, so we don't push it to the UI again here. let injected_id = acp_thread::ClientUserMessageId::new(); - let display_block = acp_thread::content::from_v1(envelope_block.clone())?; + let display_block = envelope_block.clone(); acp_thread.update(cx, |acp_thread, cx| { acp_thread.push_user_content_block_with_indent( Some(injected_id), @@ -2192,8 +2190,8 @@ impl NativeAgent { combined.extend(user_blocks); thread.update(cx, |thread, cx| { - thread.push_acp_user_block(client_user_message_id, combined, path_style, cx); - }); + thread.push_acp_user_block(client_user_message_id, combined, path_style, cx) + })?; let response_stream = thread.update(cx, |thread, cx| thread.send_existing(cx))?; @@ -2311,8 +2309,8 @@ impl NativeAgentConnection { match event { ThreadEvent::UserMessage(message) => { let content = message.content.iter().cloned() - .map(|content| acp_thread::content::from_v1(content.into())) - .collect::>>()?; + .map(acp_v2::ContentBlock::from) + .collect::>(); acp_thread.update(cx, |thread, cx| { for content in content { thread.push_user_content_block( @@ -2514,8 +2512,8 @@ impl<'a> Command<'a> { && self.skill_scope.is_none() } - fn parse(prompt: &'a [acp_v1::ContentBlock]) -> Option { - let acp_v1::ContentBlock::Text(text_content) = prompt.first()? else { + fn parse(prompt: &'a [acp_v2::ContentBlock]) -> Option { + let acp_v2::ContentBlock::Text(text_content) = prompt.first()? else { return None; }; let text = text_content.text.trim(); @@ -2868,7 +2866,9 @@ impl acp_thread::AgentConnection for NativeAgentConnection { } fn validate_prompt_content(&self, content: &[acp_v2::ContentBlock]) -> Result<()> { - acp_thread::content::validate_prompt_content_for_v1(content) + content + .iter() + .try_for_each(UserMessageContent::validate_content_block) } fn prompt( @@ -2962,10 +2962,6 @@ impl acp_thread::AgentSessionClientUserMessageIds for NativeAgentConnection { cx: &mut App, ) -> Task> { let session_id = params.session_id.clone(); - let params = match acp_thread::content::prompt_to_v1(params) { - Ok(params) => params, - Err(error) => return Task::ready(Err(error)), - }; log::info!("Received prompt request for session: {}", session_id); log::debug!("Prompt blocks count: {}", params.prompt.len()); @@ -2980,6 +2976,12 @@ impl acp_thread::AgentSessionClientUserMessageIds for NativeAgentConnection { return Task::ready(Err(anyhow::anyhow!("Session not found"))); }; + if let Err(error) = + acp_thread::AgentConnection::validate_prompt_content(self, ¶ms.prompt) + { + return Task::ready(Err(error)); + } + if let Some(parsed_command) = Command::parse(¶ms.prompt) { if parsed_command.is_unqualified(COMPACT_COMMAND_NAME) { return self.0.update(cx, |agent, cx| { @@ -3101,13 +3103,12 @@ impl acp_thread::AgentSessionClientUserMessageIds for NativeAgentConnection { }; let path_style = project_state.project.read(cx).path_style(cx); - self.run_turn(session_id, cx, move |thread, cx| { - let content: Vec = params + let content = params .prompt .into_iter() .map(|block| UserMessageContent::from_content_block(block, path_style)) - .collect::>(); + .collect::>>()?; log::debug!("Converted prompt to message: {} chars", content.len()); log::debug!("Client user message id: {:?}", client_user_message_id); log::debug!("Message content: {:?}", content); @@ -4272,6 +4273,116 @@ mod internal_tests { .collect() } + #[gpui::test] + async fn test_native_prompt_rejects_unknown_content_before_dispatch(cx: &mut TestAppContext) { + let fake = init_test(cx); + let (connection, agent, project, acp_thread) = setup_native_agent_session(cx).await; + let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone()); + let thread = cx.update(|cx| native_thread_for_session(&agent, &session_id, cx)); + let unknown = acp_v2::ContentBlock::Other(acp_v2::OtherContentBlock::new( + "_future", + std::collections::BTreeMap::from([("payload".into(), json!("private-payload"))]), + )); + + for text in ["ordinary prompt", "/compact"] { + let content = vec![text.into(), unknown.clone()]; + assert!(connection.validate_prompt_content(&content).is_err()); + let error = cx + .update(|cx| { + acp_thread::AgentSessionClientUserMessageIds::prompt( + connection.as_ref(), + ClientUserMessageId::new(), + acp_v2::PromptRequest::new(session_id.clone(), content), + cx, + ) + }) + .await + .expect_err("unsupported content must not reach command dispatch or the model"); + assert!(error.to_string().contains("Native agent does not support")); + assert!(!error.to_string().contains("private-payload")); + } + thread.update(cx, |thread, cx| { + let path_style = project.read(cx).path_style(cx); + assert!( + thread + .push_acp_user_block( + ClientUserMessageId::new(), + ["valid prefix".into(), unknown.clone()], + path_style, + cx, + ) + .is_err() + ); + assert!(thread.push_acp_agent_block(unknown, cx).is_err()); + assert!(thread.is_empty()); + }); + assert!(acp_thread.read_with(cx, |thread, _| thread.entries().is_empty())); + assert!(fake.pending_completions().is_empty()); + } + + #[gpui::test] + async fn test_native_skill_prompt_preserves_v2_attachment_source(cx: &mut TestAppContext) { + let fake = init_test(cx); + let (_connection, agent, project, acp_thread) = setup_native_agent_session(cx).await; + cx.run_until_parked(); + let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone()); + let thread = cx.update(|cx| native_thread_for_session(&agent, &session_id, cx)); + let model = fake.model("fake"); + thread.update(cx, |thread, cx| thread.set_model(model.clone(), cx)); + let mut skill = make_global_skill("test-skill", "Test skill"); + skill.embedded_body = Some("skill instructions"); + agent.update(cx, |agent, _| { + agent + .projects + .get_mut(&project.entity_id()) + .expect("project state") + .skills = Arc::new(vec![skill]); + }); + let prompt = vec![ + acp_v2::ContentBlock::Text( + acp_v2::TextContent::new("/:test-skill keep\nformatting").meta( + acp_v2::Meta::from_iter([("source".into(), json!({"raw": true}))]), + ), + ), + acp_v2::ContentBlock::ResourceLink( + acp_v2::ResourceLink::new("attachment", "https://example.com/raw%20path") + .icons(vec![acp_v2::Icon::new("https://example.com/icon.svg")]), + ), + acp_v2::ContentBlock::Resource(acp_v2::EmbeddedResource::new( + acp_v2::EmbeddedResourceResource::TextResourceContents( + acp_v2::TextResourceContents::new("attached text", "custom://raw/source").meta( + acp_v2::Meta::from_iter([("nested".into(), json!([null, true]))]), + ), + ), + )), + ]; + let send = acp_thread.update(cx, |thread, cx| thread.send(prompt.clone(), cx)); + let send = cx.foreground_executor().spawn(send); + cx.run_until_parked(); + let request = fake + .pending_completions_for(&model) + .pop() + .expect("skill reaches model"); + thread.read_with(cx, |thread, _| { + let Some(Message::User(message)) = thread.last_message() else { + panic!("skill should inject one model-context user message"); + }; + assert!(matches!(message.content.first(), Some(UserMessageContent::Text(text)) if text.contains("skill instructions"))); + assert_eq!(message.content.get(1), Some(&UserMessageContent::Text(" keep\nformatting".into()))); + assert!(matches!(message.content.get(3), Some(UserMessageContent::Text(text)) if text.contains("attached text"))); + }); + acp_thread.read_with(cx, |thread, _| { + let Some(acp_thread::AgentThreadEntry::UserMessage(message)) = thread.entries().first() + else { + panic!("skill prompt should retain its source"); + }; + assert_eq!(message.content.source_blocks(), prompt.as_slice()); + }); + fake.send_text(&model, &request, "done"); + fake.end_stream(&model, &request); + send.await.expect("skill prompt should complete"); + } + #[gpui::test] async fn test_compact_command_is_available(cx: &mut TestAppContext) { init_test(cx); @@ -4323,13 +4434,17 @@ mod internal_tests { let path_style = project.read(cx).path_style(cx); thread.update(cx, |thread, cx| { thread.set_model(model.clone(), cx); - thread.push_acp_user_block( - old_message_id, - [acp_v1::ContentBlock::from("old user")], - path_style, - cx, - ); - thread.push_acp_agent_block("old assistant".into(), cx); + thread + .push_acp_user_block( + old_message_id, + [acp_v2::ContentBlock::from("old user")], + path_style, + cx, + ) + .expect("user content should be supported"); + thread + .push_acp_agent_block("old assistant".into(), cx) + .expect("agent content should be supported"); }); }); @@ -4714,13 +4829,17 @@ mod internal_tests { let path_style = project.read(cx).path_style(cx); thread.update(cx, |thread, cx| { thread.set_model(model.clone(), cx); - thread.push_acp_user_block( - ClientUserMessageId::new(), - [acp_v1::ContentBlock::from("old user")], - path_style, - cx, - ); - thread.push_acp_agent_block("old assistant".into(), cx); + thread + .push_acp_user_block( + ClientUserMessageId::new(), + [acp_v2::ContentBlock::from("old user")], + path_style, + cx, + ) + .expect("user content should be supported"); + thread + .push_acp_agent_block("old assistant".into(), cx) + .expect("agent content should be supported"); }); }); @@ -4962,12 +5081,14 @@ mod internal_tests { cx.update(|cx| { let path_style = project.read(cx).path_style(cx); thread.update(cx, |thread, cx| { - thread.push_acp_user_block( - ClientUserMessageId::new(), - [acp_v1::ContentBlock::from("hello from the user")], - path_style, - cx, - ); + thread + .push_acp_user_block( + ClientUserMessageId::new(), + [acp_v2::ContentBlock::from("hello from the user")], + path_style, + cx, + ) + .expect("user content should be supported"); }); acp_thread.update(cx, |acp_thread, cx| { acp_thread.set_draft_prompt( @@ -5042,23 +5163,45 @@ mod internal_tests { #[test] fn test_qualified_compact_commands_are_not_native_compact() { - let unqualified_blocks = [acp_v1::ContentBlock::from("/compact")]; + let unqualified_blocks = [acp_v2::ContentBlock::from("/compact")]; let unqualified = Command::parse(&unqualified_blocks).unwrap(); assert!(unqualified.is_unqualified("compact")); - let mcp_blocks = [acp_v1::ContentBlock::from("/server.compact")]; + let mcp_blocks = [acp_v2::ContentBlock::from("/server.compact")]; let mcp_qualified = Command::parse(&mcp_blocks).unwrap(); assert_eq!(mcp_qualified.prompt_name, "compact"); assert_eq!(mcp_qualified.explicit_server_id, Some("server")); assert!(!mcp_qualified.is_unqualified("compact")); - let skill_blocks = [acp_v1::ContentBlock::from("/:compact")]; + let skill_blocks = [acp_v2::ContentBlock::from("/:compact")]; let skill_qualified = Command::parse(&skill_blocks).unwrap(); assert_eq!(skill_qualified.prompt_name, "compact"); assert_eq!(skill_qualified.skill_scope, Some("")); assert!(!skill_qualified.is_unqualified("compact")); } + #[test] + fn test_mcp_prompt_content_preserves_annotations() { + let content = json!({ + "type": "resource", + "resource": {"uri": "file:///raw%20path.md", "mimeType": "text/markdown"}, + "annotations": {"audience": ["user", "assistant"], "priority": 0.75} + }); + let block = mcp_message_content_to_acp_content_block( + serde_json::from_value(content).expect("MCP resource should deserialize"), + ); + assert_eq!( + serde_json::to_value(block).expect("v2 resource link should serialize"), + json!({ + "type": "resource_link", + "name": "file:///raw%20path.md", + "uri": "file:///raw%20path.md", + "mimeType": "text/markdown", + "annotations": {"audience": ["user", "assistant"], "priority": 0.75} + }), + ); + } + fn make_project_skill(name: &str, description: &str, worktree: &str) -> Skill { Skill { name: name.to_string(), @@ -7348,27 +7491,36 @@ mod internal_tests { cx.run_until_parked(); assert_eq!(thread_entries(&thread_store, cx), vec![]); - let send = acp_thread.update(cx, |thread, cx| { - thread.send( - vec![ - "What does ".into(), - acp_v2::ContentBlock::ResourceLink(acp_v2::ResourceLink::new( - "b.md", - MentionUri::File { - abs_path: path!("/a/b.md").into(), - } - .to_uri() - .to_string(), - )), - " mean?".into(), - ], - cx, - ) - }); + let prompt = vec![ + acp_v2::ContentBlock::Text(acp_v2::TextContent::new("What does ").meta( + acp_v2::Meta::from_iter([("source".into(), json!({"nested": [null, "retained"]}))]), + )), + acp_v2::ContentBlock::ResourceLink( + acp_v2::ResourceLink::new( + "b.md", + MentionUri::File { + abs_path: path!("/a/b.md").into(), + } + .to_uri() + .to_string(), + ) + .icons(vec![acp_v2::Icon::new("https://example.com/file.svg")]) + .annotations( + acp_v2::Annotations::new() + .audience(vec![acp_v2::Role::Other("_future-audience".into())]), + ), + ), + " mean?".into(), + ]; + let send = acp_thread.update(cx, |thread, cx| thread.send(prompt.clone(), cx)); let send = cx.foreground_executor().spawn(send); cx.run_until_parked(); let request = fake.pending_completions_for(&model).pop().unwrap(); + let request_text = request_texts_after_system(&request.messages).join("\n"); + assert!(request_text.contains("What does ")); + assert!(request_text.contains("b.md")); + assert!(request_text.contains(" mean?")); fake.send_text(&model, &request, "Lorem."); fake.send_event( &model, @@ -7390,6 +7542,13 @@ mod internal_tests { fake.end_stream(&summary_model, &summary_request); send.await.unwrap(); + acp_thread.read_with(cx, |thread, _| { + let Some(acp_thread::AgentThreadEntry::UserMessage(message)) = thread.entries().first() + else { + panic!("native prompt should retain its source"); + }; + assert_eq!(message.content.source_blocks(), prompt.as_slice()); + }); let uri = MentionUri::File { abs_path: path!("/a/b.md").into(), } @@ -8381,32 +8540,58 @@ mod internal_tests { fn mcp_message_content_to_acp_content_block( content: context_server::types::MessageContent, -) -> acp_v1::ContentBlock { +) -> acp_v2::ContentBlock { match content { - context_server::types::MessageContent::Text { - text, - annotations: _, - } => text.into(), + context_server::types::MessageContent::Text { text, annotations } => { + acp_v2::ContentBlock::Text( + acp_v2::TextContent::new(text).annotations(mcp_annotations_to_acp(annotations)), + ) + } context_server::types::MessageContent::Image { data, mime_type, - annotations: _, - } => acp_v1::ContentBlock::Image(acp_v1::ImageContent::new(data, mime_type)), + annotations, + } => acp_v2::ContentBlock::Image( + acp_v2::ImageContent::new(data, mime_type) + .annotations(mcp_annotations_to_acp(annotations)), + ), context_server::types::MessageContent::Audio { data, mime_type, - annotations: _, - } => acp_v1::ContentBlock::Audio(acp_v1::AudioContent::new(data, mime_type)), + annotations, + } => acp_v2::ContentBlock::Audio( + acp_v2::AudioContent::new(data, mime_type) + .annotations(mcp_annotations_to_acp(annotations)), + ), context_server::types::MessageContent::Resource { resource, - annotations: _, + annotations, } => { let mut link = - acp_v1::ResourceLink::new(resource.uri.to_string(), resource.uri.to_string()); + acp_v2::ResourceLink::new(resource.uri.to_string(), resource.uri.to_string()) + .annotations(mcp_annotations_to_acp(annotations)); if let Some(mime_type) = resource.mime_type { - link = link.mime_type(mime_type); + link = link.mime_type(acp_v2::MediaType::new(mime_type)); } - acp_v1::ContentBlock::ResourceLink(link) + acp_v2::ContentBlock::ResourceLink(link) } } } + +fn mcp_annotations_to_acp( + annotations: Option, +) -> Option { + annotations.map(|annotations| { + acp_v2::Annotations::new() + .audience(annotations.audience.map(|audience| { + audience + .into_iter() + .map(|role| match role { + context_server::types::Role::User => acp_v2::Role::User, + context_server::types::Role::Assistant => acp_v2::Role::Assistant, + }) + .collect::>() + })) + .priority(annotations.priority) + }) +} diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 33dad16b706..9b383117ba5 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -2726,31 +2726,36 @@ impl Thread { pub fn push_acp_user_block( &mut self, id: ClientUserMessageId, - blocks: impl IntoIterator, + blocks: impl IntoIterator, path_style: PathStyle, cx: &mut Context, - ) { + ) -> Result<()> { let content = blocks .into_iter() .map(|block| UserMessageContent::from_content_block(block, path_style)) - .collect::>(); + .collect::>>()?; self.messages .push(Arc::new(Message::User(UserMessage { id, content }))); cx.notify(); + Ok(()) } - pub fn push_acp_agent_block(&mut self, block: acp::ContentBlock, cx: &mut Context) { + pub fn push_acp_agent_block( + &mut self, + block: acp_v2::ContentBlock, + cx: &mut Context, + ) -> Result<()> { let text = match block { - acp::ContentBlock::Text(text_content) => text_content.text, - acp::ContentBlock::Image(_) => "[image]".to_string(), - acp::ContentBlock::Audio(_) => "[audio]".to_string(), - acp::ContentBlock::ResourceLink(resource_link) => resource_link.uri, - acp::ContentBlock::Resource(resource) => match resource.resource { - acp::EmbeddedResourceResource::TextResourceContents(resource) => resource.uri, - acp::EmbeddedResourceResource::BlobResourceContents(resource) => resource.uri, - _ => "[resource]".to_string(), + acp_v2::ContentBlock::Text(text_content) => text_content.text, + acp_v2::ContentBlock::Image(_) => "[image]".to_string(), + acp_v2::ContentBlock::Audio(_) => "[audio]".to_string(), + acp_v2::ContentBlock::ResourceLink(resource_link) => resource_link.uri, + acp_v2::ContentBlock::Resource(resource) => match resource.resource { + acp_v2::EmbeddedResourceResource::TextResourceContents(resource) => resource.uri, + acp_v2::EmbeddedResourceResource::BlobResourceContents(resource) => resource.uri, + _ => anyhow::bail!("Native agent does not support this embedded resource variant"), }, - _ => "[unknown]".to_string(), + _ => anyhow::bail!("Native agent does not support this content block variant"), }; self.messages.push(Arc::new(Message::Agent(AgentMessage { @@ -2758,6 +2763,7 @@ impl Thread { ..Default::default() }))); cx.notify(); + Ok(()) } fn run_turn( @@ -6912,15 +6918,32 @@ impl From for UserMessageContent { } impl UserMessageContent { - pub fn from_content_block(value: acp::ContentBlock, path_style: PathStyle) -> Self { + pub fn validate_content_block(value: &acp_v2::ContentBlock) -> Result<()> { match value { - acp::ContentBlock::Text(text_content) => Self::Text(text_content.text), - acp::ContentBlock::Image(image_content) => Self::Image(convert_image(image_content)), - acp::ContentBlock::Audio(_) => { + acp_v2::ContentBlock::Text(_) + | acp_v2::ContentBlock::Image(_) + | acp_v2::ContentBlock::Audio(_) + | acp_v2::ContentBlock::ResourceLink(_) => Ok(()), + acp_v2::ContentBlock::Resource(resource) => match &resource.resource { + acp_v2::EmbeddedResourceResource::TextResourceContents(_) + | acp_v2::EmbeddedResourceResource::BlobResourceContents(_) => Ok(()), + _ => anyhow::bail!("Native agent does not support this embedded resource variant"), + }, + _ => anyhow::bail!("Native agent does not support this content block variant"), + } + } + + pub fn from_content_block(value: acp_v2::ContentBlock, path_style: PathStyle) -> Result { + Ok(match value { + acp_v2::ContentBlock::Text(text_content) => Self::Text(text_content.text), + acp_v2::ContentBlock::Image(image_content) => Self::Image(LanguageModelImage { + source: image_content.data.into(), + }), + acp_v2::ContentBlock::Audio(_) => { // TODO Self::Text("[audio]".to_string()) } - acp::ContentBlock::ResourceLink(resource_link) => { + acp_v2::ContentBlock::ResourceLink(resource_link) => { match MentionUri::parse(&resource_link.uri, path_style) { Ok(uri) => Self::Mention { uri, @@ -6932,8 +6955,8 @@ impl UserMessageContent { } } } - acp::ContentBlock::Resource(resource) => match resource.resource { - acp::EmbeddedResourceResource::TextResourceContents(resource) => { + acp_v2::ContentBlock::Resource(resource) => match resource.resource { + acp_v2::EmbeddedResourceResource::TextResourceContents(resource) => { match MentionUri::parse(&resource.uri, path_style) { Ok(uri) => Self::Mention { uri, @@ -6951,45 +6974,35 @@ impl UserMessageContent { } } } - acp::EmbeddedResourceResource::BlobResourceContents(_) => { + acp_v2::EmbeddedResourceResource::BlobResourceContents(_) => { // TODO Self::Text("[blob]".to_string()) } - other => { - log::warn!("Unexpected content type: {:?}", other); - Self::Text("[unknown]".to_string()) - } + _ => anyhow::bail!("Native agent does not support this embedded resource variant"), }, - other => { - log::warn!("Unexpected content type: {:?}", other); - Self::Text("[unknown]".to_string()) - } - } + _ => anyhow::bail!("Native agent does not support this content block variant"), + }) } } -impl From for acp::ContentBlock { +impl From for acp_v2::ContentBlock { fn from(content: UserMessageContent) -> Self { match content { UserMessageContent::Text(text) => text.into(), UserMessageContent::Image(image) => { - acp::ContentBlock::Image(acp::ImageContent::new(image.source, "image/png")) + acp_v2::ContentBlock::Image(acp_v2::ImageContent::new(image.source, "image/png")) + } + UserMessageContent::Mention { uri, content } => { + acp_v2::ContentBlock::Resource(acp_v2::EmbeddedResource::new( + acp_v2::EmbeddedResourceResource::TextResourceContents( + acp_v2::TextResourceContents::new(content, uri.to_uri().to_string()), + ), + )) } - UserMessageContent::Mention { uri, content } => acp::ContentBlock::Resource( - acp::EmbeddedResource::new(acp::EmbeddedResourceResource::TextResourceContents( - acp::TextResourceContents::new(content, uri.to_uri().to_string()), - )), - ), } } } -fn convert_image(image_content: acp::ImageContent) -> LanguageModelImage { - LanguageModelImage { - source: image_content.data.into(), - } -} - #[cfg(test)] mod tests { use super::*;