diff --git a/components/ai_chat/core/browser/BUILD.gn b/components/ai_chat/core/browser/BUILD.gn index 20851eb6f4b1..df6def23bd78 100644 --- a/components/ai_chat/core/browser/BUILD.gn +++ b/components/ai_chat/core/browser/BUILD.gn @@ -159,6 +159,7 @@ if (!is_ios) { "//base/test:test_support", "//brave/components/ai_chat/core/browser", "//brave/components/ai_chat/core/common", + "//brave/components/ai_chat/core/common:test_support", "//brave/components/ai_chat/core/common/mojom", "//brave/components/api_request_helper", "//brave/components/brave_component_updater/browser:test_support", @@ -200,6 +201,7 @@ source_set("test_support") { deps = [ "//brave/components/ai_chat/core/browser", + "//brave/components/ai_chat/core/common:test_support", "//brave/components/ai_chat/core/common/mojom", "//services/network/public/cpp", "//testing/gmock", diff --git a/components/ai_chat/core/browser/conversation_handler_unittest.cc b/components/ai_chat/core/browser/conversation_handler_unittest.cc index a83103fb14d2..b19a413c7d41 100644 --- a/components/ai_chat/core/browser/conversation_handler_unittest.cc +++ b/components/ai_chat/core/browser/conversation_handler_unittest.cc @@ -40,6 +40,7 @@ #include "brave/components/ai_chat/core/common/features.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" #include "brave/components/ai_chat/core/common/pref_names.h" +#include "brave/components/ai_chat/core/common/test_utils.h" #include "components/grit/brave_components_strings.h" #include "components/os_crypt/async/browser/os_crypt_async.h" #include "components/os_crypt/async/browser/test_utils.h" @@ -1301,18 +1302,7 @@ TEST_F(ConversationHandlerUnitTest, UploadImage) { loop2.Run(); testing::Mock::VerifyAndClearExpectations(&client); - const std::vector> test_images = { - {0x01, 0x02, 0x03, 0x04, 0x05}, - {0xde, 0xed, 0xbe, 0xef}, - {0xff, 0xff, 0xff}, - }; - std::vector uploaded_images; - uploaded_images.emplace_back(mojom::UploadedImage::New( - "filename1", sizeof(test_images[0]), test_images[0])); - uploaded_images.emplace_back(mojom::UploadedImage::New( - "filename2", sizeof(test_images[1]), test_images[1])); - uploaded_images.emplace_back(mojom::UploadedImage::New( - "filename3", sizeof(test_images[2]), test_images[2])); + auto uploaded_images = CreateSampleUploadedImages(3); // There are uploaded images. // Note that this will need to be put at the end of this test suite @@ -1321,7 +1311,7 @@ TEST_F(ConversationHandlerUnitTest, UploadImage) { EXPECT_CALL(delegate, GetUploadedImagesSize()).WillOnce(testing::Return(3)); EXPECT_CALL(delegate, GetUploadedImages()) .Times(1) - .WillOnce(testing::Return(std::move(uploaded_images))); + .WillOnce(testing::Return(Clone(uploaded_images))); EXPECT_CALL(delegate, ClearUploadedImages()).Times(1); base::RunLoop loop3; EXPECT_CALL(client, OnModelDataChanged) @@ -1338,15 +1328,11 @@ TEST_F(ConversationHandlerUnitTest, UploadImage) { auto& last_entry = conversation_handler_->GetConversationHistory().back(); EXPECT_TRUE(last_entry->uploaded_images); const auto& images = last_entry->uploaded_images.value(); - EXPECT_EQ(images[0]->filename, "filename1"); - EXPECT_EQ(images[0]->filesize, static_cast(sizeof(test_images[0]))); - EXPECT_EQ(images[0]->image_data, test_images[0]); - EXPECT_EQ(images[1]->filename, "filename2"); - EXPECT_EQ(images[1]->filesize, static_cast(sizeof(test_images[1]))); - EXPECT_EQ(images[1]->image_data, test_images[1]); - EXPECT_EQ(images[2]->filename, "filename3"); - EXPECT_EQ(images[2]->filesize, static_cast(sizeof(test_images[2]))); - EXPECT_EQ(images[2]->image_data, test_images[2]); + for (size_t i = 0; i < images.size(); ++i) { + EXPECT_EQ(images[i]->filename, uploaded_images[i]->filename); + EXPECT_EQ(images[i]->filesize, uploaded_images[i]->filesize); + EXPECT_EQ(images[i]->image_data, uploaded_images[i]->image_data); + } } TEST_F(ConversationHandlerUnitTest_NoAssociatedContent, diff --git a/components/ai_chat/core/browser/engine/engine_consumer.cc b/components/ai_chat/core/browser/engine/engine_consumer.cc index 657e255d4a4d..b86d1c370cb4 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer.cc +++ b/components/ai_chat/core/browser/engine/engine_consumer.cc @@ -7,8 +7,9 @@ #include #include -#include +#include "base/base64.h" +#include "base/strings/strcat.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" namespace ai_chat { @@ -29,6 +30,11 @@ bool EngineConsumer::SupportsDeltaTextResponses() const { return false; } +std::string EngineConsumer::GetImageDataURL(base::span image_data) { + constexpr char kDataUrlPrefix[] = "data:image/png;base64,"; + return base::StrCat({kDataUrlPrefix, base::Base64Encode(image_data)}); +} + bool EngineConsumer::CanPerformCompletionRequest( const ConversationHistory& conversation_history) const { if (conversation_history.empty()) { diff --git a/components/ai_chat/core/browser/engine/engine_consumer.h b/components/ai_chat/core/browser/engine/engine_consumer.h index 68c4b0b1bd28..5d73f746a8a0 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer.h +++ b/components/ai_chat/core/browser/engine/engine_consumer.h @@ -91,6 +91,8 @@ class EngineConsumer { max_associated_content_length_ = max_associated_content_length; } + static std::string GetImageDataURL(base::span image_data); + protected: // Check if we should call GenerationCompletedCallback early based on the // conversation history. Ex. empty history, or if the last entry is not a diff --git a/components/ai_chat/core/browser/engine/engine_consumer_claude.h b/components/ai_chat/core/browser/engine/engine_consumer_claude.h index 96485d86a042..761dbd26745f 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_claude.h +++ b/components/ai_chat/core/browser/engine/engine_consumer_claude.h @@ -9,7 +9,6 @@ #include #include #include -#include #include "base/memory/weak_ptr.h" #include "brave/components/ai_chat/core/browser/ai_chat_credential_manager.h" diff --git a/components/ai_chat/core/browser/engine/engine_consumer_conversation_api.cc b/components/ai_chat/core/browser/engine/engine_consumer_conversation_api.cc index 3f5b6299ef9c..132215c4282d 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_conversation_api.cc +++ b/components/ai_chat/core/browser/engine/engine_consumer_conversation_api.cc @@ -11,7 +11,6 @@ #include #include -#include "base/base64.h" #include "base/check.h" #include "base/functional/bind.h" #include "base/functional/callback.h" @@ -20,7 +19,6 @@ #include "base/memory/weak_ptr.h" #include "base/numerics/clamped_math.h" #include "base/strings/string_split.h" -#include "base/strings/string_util.h" #include "base/time/time.h" #include "base/types/expected.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" @@ -121,17 +119,15 @@ void EngineConsumerConversationAPI::GenerateAssistantResponse( const auto& last_entry = conversation_history.back(); if (last_entry->uploaded_images) { size_t counter = 0; - constexpr char kImageUrl[] = R"(data:image/png;base64,$1)"; for (const auto& uploaded_image : last_entry->uploaded_images.value()) { // Only send the first uploaded_image becasue llama-vision seems to take // the last one if there are multiple images if (counter++ > 0) { break; } - const std::string image_url = base::ReplaceStringPlaceholders( - kImageUrl, {base::Base64Encode(uploaded_image->image_data)}, nullptr); conversation.push_back({mojom::CharacterType::HUMAN, - ConversationEventType::UploadImage, image_url}); + ConversationEventType::UploadImage, + GetImageDataURL(uploaded_image->image_data)}); } } // history diff --git a/components/ai_chat/core/browser/engine/engine_consumer_conversation_api_unittest.cc b/components/ai_chat/core/browser/engine/engine_consumer_conversation_api_unittest.cc index da9a72cd7b2e..e022e9bed0cd 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_conversation_api_unittest.cc +++ b/components/ai_chat/core/browser/engine/engine_consumer_conversation_api_unittest.cc @@ -11,6 +11,7 @@ #include #include +#include "base/base64.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/json/json_writer.h" @@ -19,6 +20,7 @@ #include "base/run_loop.h" #include "base/test/bind.h" #include "base/test/task_environment.h" +#include "base/test/test_future.h" #include "base/test/values_test_util.h" #include "base/time/time.h" #include "base/types/expected.h" @@ -26,6 +28,7 @@ #include "brave/components/ai_chat/core/browser/engine/conversation_api_client.h" #include "brave/components/ai_chat/core/browser/engine/engine_consumer.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" +#include "brave/components/ai_chat/core/common/test_utils.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -435,11 +438,7 @@ TEST_F(EngineConsumerConversationAPIUnitTest, GenerateEvents_SummarizePage) { } TEST_F(EngineConsumerConversationAPIUnitTest, GenerateEvents_UploadImage) { - const std::vector> test_images = { - {0x01, 0x02, 0x03, 0x04, 0x05}, - {0xde, 0xed, 0xbe, 0xef}, - {0xff, 0xff, 0xff}, - }; + auto uploaded_images = CreateSampleUploadedImages(3); constexpr char kTestPrompt[] = "Tell the user what is in the image?"; constexpr char kAssistantResponse[] = "It's a lion!"; auto* mock_api_client = GetMockConversationAPIClient(); @@ -452,7 +451,10 @@ TEST_F(EngineConsumerConversationAPIUnitTest, GenerateEvents_UploadImage) { // Only support one image for now. ASSERT_EQ(conversation.size(), 2u); EXPECT_EQ(conversation[0].role, mojom::CharacterType::HUMAN); - EXPECT_EQ(conversation[0].content, "data:image/png;base64,AQIDBAU="); + EXPECT_EQ( + conversation[0].content, + base::StrCat({"data:image/png;base64,", + base::Base64Encode(uploaded_images[0]->image_data)})); EXPECT_EQ(conversation[0].type, ConversationAPIClient::UploadImage); EXPECT_EQ(conversation[1].role, mojom::CharacterType::HUMAN); EXPECT_EQ(conversation[1].content, kTestPrompt); @@ -460,28 +462,17 @@ TEST_F(EngineConsumerConversationAPIUnitTest, GenerateEvents_UploadImage) { std::move(callback).Run(kAssistantResponse); }); - std::vector uploaded_images; - uploaded_images.emplace_back( - mojom::UploadedImage::New("filename1", 1, test_images[0])); - uploaded_images.emplace_back( - mojom::UploadedImage::New("filename", 2, test_images[1])); - uploaded_images.emplace_back( - mojom::UploadedImage::New("filename", 3, test_images[2])); - std::vector history; history.push_back(mojom::ConversationTurn::New( std::nullopt, mojom::CharacterType::HUMAN, mojom::ActionType::UNSPECIFIED, "What is this image?", kTestPrompt, std::nullopt, std::nullopt, - base::Time::Now(), std::nullopt, std::move(uploaded_images), false)); + base::Time::Now(), std::nullopt, CloneUpdatedImages(uploaded_images), + false)); - engine_->GenerateAssistantResponse( - false, "", history, "", base::DoNothing(), - base::BindLambdaForTesting([&run_loop, kAssistantResponse]( - EngineConsumer::GenerationResult result) { - EXPECT_STREQ(result.value().c_str(), kAssistantResponse); - run_loop.Quit(); - })); - run_loop.Run(); + base::test::TestFuture future; + engine_->GenerateAssistantResponse(false, "", history, "", base::DoNothing(), + future.GetCallback()); + EXPECT_STREQ(future.Take()->c_str(), kAssistantResponse); testing::Mock::VerifyAndClearExpectations(mock_api_client); } diff --git a/components/ai_chat/core/browser/engine/engine_consumer_llama.h b/components/ai_chat/core/browser/engine/engine_consumer_llama.h index 5c2d8e946bec..61d732aeabe6 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_llama.h +++ b/components/ai_chat/core/browser/engine/engine_consumer_llama.h @@ -9,7 +9,6 @@ #include #include #include -#include #include "base/memory/weak_ptr.h" #include "brave/components/ai_chat/core/browser/ai_chat_credential_manager.h" diff --git a/components/ai_chat/core/browser/engine/engine_consumer_oai.cc b/components/ai_chat/core/browser/engine/engine_consumer_oai.cc index 86bdc2eb1de0..ee09a7ce3ac6 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_oai.cc +++ b/components/ai_chat/core/browser/engine/engine_consumer_oai.cc @@ -11,7 +11,6 @@ #include #include -#include "base/base64.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" @@ -101,7 +100,6 @@ base::Value::List BuildMessages( user_message.Set("text", "These images are uploaded by the users"); content.Append(std::move(user_message)); size_t counter = 0; - constexpr char kImageUrl[] = R"(data:image/png;base64,$1)"; // Only send the first uploaded_image becasue llama-vision seems to take the // last one if there are multiple uploaded_images for (const auto& uploaded_image : last_entry->uploaded_images.value()) { @@ -110,10 +108,9 @@ base::Value::List BuildMessages( } base::Value::Dict image; image.Set("type", "image_url"); - const std::string image_url = base::ReplaceStringPlaceholders( - kImageUrl, {base::Base64Encode(uploaded_image->image_data)}, nullptr); base::Value::Dict image_url_dict; - image_url_dict.Set("url", image_url); + image_url_dict.Set( + "url", EngineConsumer::GetImageDataURL(uploaded_image->image_data)); image.Set("image_url", std::move(image_url_dict)); content.Append(std::move(image)); } diff --git a/components/ai_chat/core/browser/engine/engine_consumer_oai.h b/components/ai_chat/core/browser/engine/engine_consumer_oai.h index 7a998a952acd..94d2283b504a 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_oai.h +++ b/components/ai_chat/core/browser/engine/engine_consumer_oai.h @@ -9,7 +9,6 @@ #include #include #include -#include #include "base/memory/weak_ptr.h" #include "brave/components/ai_chat/core/browser/ai_chat_credential_manager.h" diff --git a/components/ai_chat/core/browser/engine/engine_consumer_oai_unittest.cc b/components/ai_chat/core/browser/engine/engine_consumer_oai_unittest.cc index 439e77a04d5d..e228495367a3 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer_oai_unittest.cc +++ b/components/ai_chat/core/browser/engine/engine_consumer_oai_unittest.cc @@ -11,6 +11,7 @@ #include #include +#include "base/base64.h" #include "base/containers/checked_iterators.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" @@ -22,12 +23,14 @@ #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" #include "base/test/task_environment.h" +#include "base/test/test_future.h" #include "base/test/values_test_util.h" #include "base/time/time.h" #include "base/values.h" #include "brave/components/ai_chat/core/browser/engine/engine_consumer.h" #include "brave/components/ai_chat/core/browser/engine/test_utils.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom-forward.h" +#include "brave/components/ai_chat/core/common/test_utils.h" #include "components/grit/brave_components_strings.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "testing/gmock/include/gmock/gmock.h" @@ -446,35 +449,35 @@ TEST_F(EngineConsumerOAIUnitTest, GenerateAssistantResponseEarlyReturn) { TEST_F(EngineConsumerOAIUnitTest, GenerateAssistantResponseUploadImage) { EngineConsumer::ConversationHistory history; auto* client = GetClient(); - auto run_loop = std::make_unique(); - const std::vector> test_images = { - {0x01, 0x02, 0x03, 0x04, 0x05}, - {0xde, 0xed, 0xbe, 0xef}, - {0xff, 0xff, 0xff}, - }; + auto uploaded_images = CreateSampleUploadedImages(3); constexpr char kTestPrompt[] = "Tell the user what is in the image?"; constexpr char kAssistantResponse[] = "It's a lion!"; EXPECT_CALL(*client, PerformRequest(_, _, _, _)) .WillOnce( - [kTestPrompt, kAssistantResponse]( + [kTestPrompt, kAssistantResponse, &uploaded_images]( const mojom::CustomModelOptions, base::Value::List messages, EngineConsumer::GenerationDataCallback, EngineConsumer::GenerationCompletedCallback completed_callback) { EXPECT_EQ(*messages[0].GetDict().Find("role"), "system"); - auto expected_dict = ParseJsonDict(R"({ + constexpr char kJsonTemplate[] = R"({ "content": [ { "text": "These images are uploaded by the users", "type": "text" }, { "image_url": { - "url": "data:image/png;base64,AQIDBAU=" + "url": "data:image/png;base64,$1" }, "type": "image_url" } ], "role": "user" } - )"); + )"; + const std::string json_str = base::ReplaceStringPlaceholders( + kJsonTemplate, + {base::Base64Encode(uploaded_images[0]->image_data)}, nullptr); + auto expected_dict = ParseJsonDict(json_str); + EXPECT_EQ(messages[1].GetDict(), expected_dict); EXPECT_EQ(*messages[2].GetDict().Find("role"), "user"); @@ -484,27 +487,15 @@ TEST_F(EngineConsumerOAIUnitTest, GenerateAssistantResponseUploadImage) { .Run(EngineConsumer::GenerationResult(kAssistantResponse)); }); - std::vector uploaded_images; - uploaded_images.emplace_back( - mojom::UploadedImage::New("filename1", 1, test_images[0])); - uploaded_images.emplace_back( - mojom::UploadedImage::New("filename", 2, test_images[1])); - uploaded_images.emplace_back( - mojom::UploadedImage::New("filename", 3, test_images[2])); - history.push_back(mojom::ConversationTurn::New( std::nullopt, mojom::CharacterType::HUMAN, mojom::ActionType::UNSPECIFIED, "What is this image?", kTestPrompt, std::nullopt, std::nullopt, - base::Time::Now(), std::nullopt, std::move(uploaded_images), false)); - - engine_->GenerateAssistantResponse( - false, "", history, "", base::DoNothing(), - base::BindLambdaForTesting([&run_loop, kAssistantResponse]( - EngineConsumer::GenerationResult result) { - EXPECT_STREQ(result.value().c_str(), kAssistantResponse); - run_loop->Quit(); - })); - run_loop->Run(); + base::Time::Now(), std::nullopt, CloneUpdatedImages(uploaded_images), + false)); + base::test::TestFuture future; + engine_->GenerateAssistantResponse(false, "", history, "", base::DoNothing(), + future.GetCallback()); + EXPECT_STREQ(future.Take()->c_str(), kAssistantResponse); testing::Mock::VerifyAndClearExpectations(client); } diff --git a/components/ai_chat/core/browser/test_utils.cc b/components/ai_chat/core/browser/test_utils.cc index f753925df162..815f9a8c8e54 100644 --- a/components/ai_chat/core/browser/test_utils.cc +++ b/components/ai_chat/core/browser/test_utils.cc @@ -8,14 +8,13 @@ #include #include -#include "base/rand_util.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/time/time.h" #include "base/uuid.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom-forward.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" -#include "crypto/random.h" +#include "brave/components/ai_chat/core/common/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace ai_chat { @@ -221,15 +220,9 @@ std::vector CreateSampleChatHistory( for (size_t i = 0; i < num_query_pairs; i++) { // query std::optional> uploaded_images; - for (size_t j = 0; j < num_uploaded_images_per_query; ++j) { - std::vector image_data(base::RandGenerator(64)); - crypto::RandBytes(image_data); - if (!uploaded_images) { - uploaded_images = std::vector(); - } - uploaded_images->emplace_back(mojom::UploadedImage::New( - "filename" + base::NumberToString(base::RandUint64()), - sizeof(image_data), image_data)); + if (num_uploaded_images_per_query) { + uploaded_images = + CreateSampleUploadedImages(num_uploaded_images_per_query); } history.push_back(mojom::ConversationTurn::New( base::Uuid::GenerateRandomV4().AsLowercaseString(), diff --git a/components/ai_chat/core/common/BUILD.gn b/components/ai_chat/core/common/BUILD.gn index 25604afdac5d..b7b17e48e7ff 100644 --- a/components/ai_chat/core/common/BUILD.gn +++ b/components/ai_chat/core/common/BUILD.gn @@ -25,6 +25,20 @@ component("common") { ] } +source_set("test_support") { + testonly = true + sources = [ + "test_utils.cc", + "test_utils.h", + ] + + deps = [ + "//base", + "//brave/components/ai_chat/core/common/mojom", + "//crypto", + ] +} + if (!is_ios) { source_set("unit_tests") { testonly = true diff --git a/components/ai_chat/core/common/DEPS b/components/ai_chat/core/common/DEPS new file mode 100644 index 000000000000..259013b159fb --- /dev/null +++ b/components/ai_chat/core/common/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+crypto", +] + diff --git a/components/ai_chat/core/common/test_utils.cc b/components/ai_chat/core/common/test_utils.cc new file mode 100644 index 000000000000..64e6dfe58021 --- /dev/null +++ b/components/ai_chat/core/common/test_utils.cc @@ -0,0 +1,33 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/ai_chat/core/common/test_utils.h" + +#include "base/rand_util.h" +#include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" +#include "crypto/random.h" + +namespace ai_chat { + +std::vector CreateSampleUploadedImages(size_t number) { + std::vector uploaded_images; + for (size_t i = 0; i < number; ++i) { + std::vector image_data(base::RandGenerator(64)); + crypto::RandBytes(image_data); + uploaded_images.emplace_back(mojom::UploadedImage::New( + "filename" + base::NumberToString(i), sizeof(image_data), image_data)); + } + return uploaded_images; +} +std::vector CloneUpdatedImages( + const std::vector& input) { + std::vector output; + for (const auto& image : input) { + output.emplace_back(image.Clone()); + } + return output; +} + +} // namespace ai_chat diff --git a/components/ai_chat/core/common/test_utils.h b/components/ai_chat/core/common/test_utils.h new file mode 100644 index 000000000000..7489e5ebf63a --- /dev/null +++ b/components/ai_chat/core/common/test_utils.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_AI_CHAT_CORE_COMMON_TEST_UTILS_H_ +#define BRAVE_COMPONENTS_AI_CHAT_CORE_COMMON_TEST_UTILS_H_ + +#include + +#include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom-forward.h" + +namespace ai_chat { + +std::vector CreateSampleUploadedImages(size_t number); + +std::vector CloneUpdatedImages( + const std::vector& input); + +} // namespace ai_chat + +#endif // BRAVE_COMPONENTS_AI_CHAT_CORE_COMMON_TEST_UTILS_H_