11#include "absl/strings/str_cat.h"
12#include "absl/strings/str_format.h"
13#include "absl/strings/str_split.h"
14#include "absl/strings/strip.h"
15#include "absl/time/clock.h"
16#include "absl/time/time.h"
24#include <TargetConditionals.h>
27#if defined(__APPLE__) && \
28 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
30#define YAZE_AI_IOS_URLSESSION 1
38#include "nlohmann/json.hpp"
41#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
42#include <openssl/crypto.h>
43#include <openssl/err.h>
44#include <openssl/ssl.h>
47static std::atomic<bool> g_openssl_initialized{
false};
48static std::mutex g_openssl_init_mutex;
50static void InitializeOpenSSL() {
51 std::lock_guard<std::mutex> lock(g_openssl_init_mutex);
52 if (!g_openssl_initialized.exchange(
true)) {
54 OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS,
56 std::cerr <<
"✓ OpenSSL initialized for HTTPS support" << std::endl;
69 auto declarations_or =
71 if (!declarations_or.ok()) {
72 return declarations_or.status();
80 : function_calling_enabled_(config.use_function_calling), config_(config) {
81 if (config_.verbose) {
82 std::cerr <<
"[DEBUG] Initializing Gemini service..." << std::endl;
83 std::cerr <<
"[DEBUG] Function calling: "
84 << (function_calling_enabled_ ?
"enabled" :
"disabled")
86 std::cerr <<
"[DEBUG] Prompt version: " << config_.prompt_version
90#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
93 if (config_.verbose) {
94 std::cerr <<
"[DEBUG] OpenSSL initialized for HTTPS" << std::endl;
99 std::string catalogue_path = config_.prompt_version ==
"v2"
100 ?
"assets/agent/prompt_catalogue_v2.yaml"
101 :
"assets/agent/prompt_catalogue.yaml";
102 if (
auto status = prompt_builder_.LoadResourceCatalogue(catalogue_path);
104 std::cerr <<
"⚠️ Failed to load agent prompt catalogue: "
105 << status.message() << std::endl;
108 if (config_.verbose) {
109 std::cerr <<
"[DEBUG] Loaded prompt catalogue" << std::endl;
112 if (config_.system_instruction.empty()) {
113 if (config_.verbose) {
114 std::cerr <<
"[DEBUG] Building system instruction..." << std::endl;
118 std::string prompt_file;
119 if (config_.prompt_version ==
"v3") {
120 prompt_file =
"agent/system_prompt_v3.txt";
121 }
else if (config_.prompt_version ==
"v2") {
122 prompt_file =
"agent/system_prompt_v2.txt";
124 prompt_file =
"agent/system_prompt.txt";
130 if (prompt_path.ok()) {
131 std::ifstream file(prompt_path->string());
133 std::stringstream buffer;
134 buffer << file.rdbuf();
135 config_.system_instruction = buffer.str();
136 if (config_.verbose) {
137 std::cerr <<
"[DEBUG] Loaded prompt: " << prompt_path->string()
146 if (config_.use_enhanced_prompting) {
147 config_.system_instruction =
148 prompt_builder_.BuildSystemInstructionWithExamples();
150 config_.system_instruction = BuildSystemInstruction();
155 if (config_.verbose) {
156 std::cerr <<
"[DEBUG] Gemini service initialized" << std::endl;
161 function_calling_enabled_ = enable;
165 return {
"resource-list",
"resource-search",
166 "dungeon-list-sprites",
"dungeon-describe-room",
167 "overworld-find-tile",
"overworld-describe-map",
168 "overworld-list-warps"};
171std::string GeminiAIService::BuildSystemInstruction() {
174 return prompt_builder_.BuildSystemInstruction();
178 prompt_builder_.SetRom(rom);
182#ifndef YAZE_WITH_JSON
183 return absl::UnimplementedError(
"Gemini AI service requires JSON support");
185 if (config_.api_key.empty()) {
187 std::vector<ModelInfo> defaults = {
188 {.name =
"gemini-3.0-preview",
189 .display_name =
"Gemini 3.0 Preview",
191 .description =
"Cutting-edge model, currently in preview"},
192 {.name =
"gemini-3.0-flash-preview",
193 .display_name =
"Gemini 3.0 Flash Preview",
195 .description =
"Fastest preview model"},
196 {.name =
"gemini-2.5-pro",
197 .display_name =
"Gemini 2.5 Pro",
199 .description =
"High intelligence for complex tasks"},
200 {.name =
"gemini-2.5-flash",
201 .display_name =
"Gemini 2.5 Flash",
203 .description =
"Fastest multimodal model"}};
208 std::string endpoint =
209 "https://generativelanguage.googleapis.com/v1beta/models?key=" +
212 if (config_.verbose) {
213 std::cerr <<
"[DEBUG] Listing models: "
214 << endpoint.substr(0, endpoint.find(
"key=")) <<
"...'"
218 std::string response_str;
219#if defined(YAZE_AI_IOS_URLSESSION)
222 if (config_.verbose) {
223 std::cerr <<
"[DEBUG] Gemini models request failed: "
224 << resp_or.status().message() << std::endl;
226 return absl::InternalError(
"Failed to list Gemini models");
228 response_str = resp_or->body;
231 std::string curl_cmd =
"curl -s -X GET '" + endpoint +
"' 2>&1";
234 FILE* pipe = _popen(curl_cmd.c_str(),
"r");
236 FILE* pipe = popen(curl_cmd.c_str(),
"r");
239 return absl::InternalError(
"Failed to execute curl command");
243 while (fgets(buffer,
sizeof(buffer), pipe) !=
nullptr) {
244 response_str += buffer;
254 auto models_json = nlohmann::json::parse(response_str,
nullptr,
false);
255 if (models_json.is_discarded()) {
256 return absl::InternalError(
"Failed to parse Gemini models JSON");
259 if (!models_json.contains(
"models")) {
261 std::vector<ModelInfo> defaults = {{.name =
"gemini-2.5-flash",
262 .display_name =
"Gemini 2.0 Flash",
264 {.name =
"gemini-1.5-flash",
265 .display_name =
"Gemini 1.5 Flash",
267 {.name =
"gemini-1.5-pro",
268 .display_name =
"Gemini 1.5 Pro",
273 std::vector<ModelInfo> models;
274 for (
const auto& m : models_json[
"models"]) {
275 std::string
name = m.value(
"name",
"");
277 if (absl::StartsWith(name,
"models/")) {
282 if (absl::StartsWith(name,
"gemini")) {
285 info.display_name = m.value(
"displayName", name);
287 info.description = m.value(
"description",
"");
288 info.family =
"gemini";
289 info.is_local =
false;
290 models.push_back(std::move(info));
295 }
catch (
const std::exception& e) {
296 return absl::InternalError(
297 absl::StrCat(
"Failed to list models: ", e.what()));
303#ifndef YAZE_WITH_JSON
304 return absl::UnimplementedError(
305 "Gemini AI service requires JSON support. Build with "
306 "-DYAZE_WITH_JSON=ON");
309 if (config_.verbose) {
310 std::cerr <<
"[DEBUG] CheckAvailability: start" << std::endl;
313 if (config_.api_key.empty()) {
314 return absl::FailedPreconditionError(
315 "❌ Gemini API key not configured\n"
316 " Set GEMINI_API_KEY environment variable\n"
317 " Get your API key at: https://makersuite.google.com/app/apikey");
320 if (config_.verbose) {
321 std::cerr <<
"[DEBUG] CheckAvailability: creating HTTPS client"
325 httplib::Client cli(
"https://generativelanguage.googleapis.com");
326 if (config_.verbose) {
327 std::cerr <<
"[DEBUG] CheckAvailability: client created" << std::endl;
330 cli.set_connection_timeout(5, 0);
332 if (config_.verbose) {
333 std::cerr <<
"[DEBUG] CheckAvailability: building endpoint" << std::endl;
335 std::string test_endpoint =
"/v1beta/models/" + config_.model;
336 httplib::Headers headers = {
337 {
"x-goog-api-key", config_.api_key},
340 if (config_.verbose) {
341 std::cerr <<
"[DEBUG] CheckAvailability: making request to "
342 << test_endpoint << std::endl;
344 auto res = cli.Get(test_endpoint.c_str(), headers);
346 if (config_.verbose) {
347 std::cerr <<
"[DEBUG] CheckAvailability: got response" << std::endl;
351 return absl::UnavailableError(
352 "❌ Cannot reach Gemini API\n"
353 " Check your internet connection");
356 if (res->status == 401 || res->status == 403) {
357 return absl::PermissionDeniedError(
358 "❌ Invalid Gemini API key\n"
359 " Verify your key at: https://makersuite.google.com/app/apikey");
362 if (res->status == 404) {
363 return absl::NotFoundError(
364 absl::StrCat(
"❌ Model '", config_.model,
"' not found\n",
365 " Try: gemini-2.5-flash or gemini-1.5-pro"));
368 if (res->status != 200) {
369 return absl::InternalError(absl::StrCat(
370 "❌ Gemini API error: ", res->status,
"\n ", res->body));
373 return absl::OkStatus();
374 }
catch (
const std::exception& e) {
375 if (config_.verbose) {
376 std::cerr <<
"[DEBUG] CheckAvailability: EXCEPTION: " << e.what()
379 return absl::InternalError(
380 absl::StrCat(
"Exception during availability check: ", e.what()));
382 if (config_.verbose) {
383 std::cerr <<
"[DEBUG] CheckAvailability: UNKNOWN EXCEPTION" << std::endl;
385 return absl::InternalError(
"Unknown exception during availability check");
391 const std::string& prompt) {
397 const std::vector<agent::ChatMessage>& history) {
398#ifndef YAZE_WITH_JSON
399 return absl::UnimplementedError(
400 "Gemini AI service requires JSON support. Build with "
401 "-DYAZE_WITH_JSON=ON");
403 if (history.empty()) {
404 return absl::InvalidArgumentError(
"History cannot be empty.");
409 std::string prompt = prompt_builder_.BuildPromptFromHistory(history);
417 if (config_.api_key.empty()) {
418 return absl::FailedPreconditionError(
"Gemini API key not configured");
421 absl::Time request_start = absl::Now();
424 if (config_.verbose) {
425 std::cerr <<
"[DEBUG] Using curl for HTTPS request" << std::endl;
426 std::cerr <<
"[DEBUG] Processing " << history.size()
427 <<
" messages in history" << std::endl;
432 nlohmann::json contents = nlohmann::json::array();
435 int start_idx = std::max(0,
static_cast<int>(history.size()) - 10);
436 for (
size_t i = start_idx; i < history.size(); ++i) {
437 const auto& msg = history[i];
441 nlohmann::json message = {{
"role", role},
442 {
"parts", {{{
"text", msg.message}}}}};
443 contents.push_back(message);
448 if (!history.empty() &&
451 nlohmann::json user_continuation = {
453 {
"parts", {{{
"text",
"Please continue or clarify your response."}}}}};
454 contents.push_back(user_continuation);
458 nlohmann::json request_body = {
459 {
"system_instruction",
460 {{
"parts", {{
"text", config_.system_instruction}}}}},
461 {
"contents", contents},
463 {{
"temperature", config_.temperature},
464 {
"maxOutputTokens", config_.max_output_tokens}}}};
466 if (config_.verbose) {
467 std::cerr <<
"[DEBUG] Sending " << contents.size()
468 <<
" conversation turns to Gemini" << std::endl;
473 if (!function_calling_enabled_) {
474 request_body[
"generationConfig"][
"responseMimeType"] =
"application/json";
478 if (function_calling_enabled_) {
480 if (!tools_or.ok()) {
481 if (config_.verbose) {
482 std::cerr <<
"[DEBUG] Function calling schemas unavailable: "
483 << tools_or.status().message() << std::endl;
485 }
else if (!tools_or->empty()) {
486 if (config_.verbose) {
487 std::string tools_str = tools_or->dump();
488 std::cerr <<
"[DEBUG] Function calling schemas: "
489 << tools_str.substr(0, 200) <<
"..." << std::endl;
492 request_body[
"tools"] = *tools_or;
496 std::string endpoint =
497 "https://generativelanguage.googleapis.com/v1beta/models/" +
498 config_.model +
":generateContent";
499 std::string response_str;
500#if defined(YAZE_AI_IOS_URLSESSION)
501 std::map<std::string, std::string> headers;
502 headers.emplace(
"Content-Type",
"application/json");
503 headers.emplace(
"x-goog-api-key", config_.api_key);
505 request_body.dump(), 60000);
507 return resp_or.status();
509 if (resp_or->status_code != 200) {
510 return absl::InternalError(absl::StrCat(
511 "Gemini API error: ", resp_or->status_code,
"\n", resp_or->body));
513 response_str = resp_or->body;
516 std::string temp_file =
"/tmp/gemini_request.json";
517 std::ofstream out(temp_file);
518 out << request_body.dump();
522 std::string curl_cmd =
"curl -s -X POST '" + endpoint +
524 "-H 'Content-Type: application/json' "
525 "-H 'x-goog-api-key: " +
531 if (config_.verbose) {
532 std::cerr <<
"[DEBUG] Executing API request..." << std::endl;
536 FILE* pipe = _popen(curl_cmd.c_str(),
"r");
538 FILE* pipe = popen(curl_cmd.c_str(),
"r");
541 return absl::InternalError(
"Failed to execute curl command");
545 while (fgets(buffer,
sizeof(buffer), pipe) !=
nullptr) {
546 response_str += buffer;
550 int status = _pclose(pipe);
552 int status = pclose(pipe);
554 std::remove(temp_file.c_str());
557 return absl::InternalError(
558 absl::StrCat(
"Curl failed with status ", status));
562 if (response_str.empty()) {
563 return absl::InternalError(
"Empty response from Gemini API");
567 if (config_.verbose) {
570 <<
"🔍 Raw Gemini API Response:"
573 <<
"\033[2m" << response_str.substr(0, 500) <<
"\033[0m"
577 if (config_.verbose) {
578 std::cerr <<
"[DEBUG] Parsing response..." << std::endl;
580 auto parsed_or = ParseGeminiResponse(response_str);
581 if (!parsed_or.ok()) {
582 return parsed_or.status();
584 AgentResponse agent_response = std::move(parsed_or.value());
586 agent_response.model = config_.model;
587 agent_response.latency_seconds =
588 absl::ToDoubleSeconds(absl::Now() - request_start);
589 agent_response.parameters[
"prompt_version"] = config_.prompt_version;
590 agent_response.parameters[
"temperature"] =
591 absl::StrFormat(
"%.2f", config_.temperature);
592 agent_response.parameters[
"max_output_tokens"] =
593 absl::StrFormat(
"%d", config_.max_output_tokens);
594 agent_response.parameters[
"function_calling"] =
595 function_calling_enabled_ ?
"true" :
"false";
596 return agent_response;
598 }
catch (
const std::exception& e) {
599 if (config_.verbose) {
600 std::cerr <<
"[ERROR] Exception: " << e.what() << std::endl;
602 return absl::InternalError(
603 absl::StrCat(
"Exception during generation: ", e.what()));
605 if (config_.verbose) {
606 std::cerr <<
"[ERROR] Unknown exception" << std::endl;
608 return absl::InternalError(
"Unknown exception during generation");
613absl::StatusOr<AgentResponse> GeminiAIService::ParseGeminiResponse(
614 const std::string& response_body) {
615#ifndef YAZE_WITH_JSON
616 return absl::UnimplementedError(
"JSON support required");
618 AgentResponse agent_response;
620 auto response_json = nlohmann::json::parse(response_body,
nullptr,
false);
621 if (response_json.is_discarded()) {
622 return absl::InternalError(
"❌ Failed to parse Gemini response JSON");
626 if (!response_json.contains(
"candidates") ||
627 response_json[
"candidates"].empty()) {
628 return absl::InternalError(
"❌ No candidates in Gemini response");
631 for (
const auto& candidate : response_json[
"candidates"]) {
632 if (!candidate.contains(
"content") ||
633 !candidate[
"content"].contains(
"parts")) {
637 for (
const auto& part : candidate[
"content"][
"parts"]) {
638 if (part.contains(
"text")) {
639 std::string text_content = part[
"text"].get<std::string>();
642 if (config_.verbose) {
645 <<
"🔍 Raw LLM Response:"
648 <<
"\033[2m" << text_content <<
"\033[0m"
653 text_content = std::string(absl::StripAsciiWhitespace(text_content));
654 if (absl::StartsWith(text_content,
"```json")) {
655 text_content = text_content.substr(7);
656 }
else if (absl::StartsWith(text_content,
"```")) {
657 text_content = text_content.substr(3);
659 if (absl::EndsWith(text_content,
"```")) {
660 text_content = text_content.substr(0, text_content.length() - 3);
662 text_content = std::string(absl::StripAsciiWhitespace(text_content));
665 auto parsed_text = nlohmann::json::parse(text_content,
nullptr,
false);
666 if (!parsed_text.is_discarded()) {
668 if (parsed_text.contains(
"text_response") &&
669 parsed_text[
"text_response"].is_string()) {
670 agent_response.text_response =
671 parsed_text[
"text_response"].get<std::string>();
675 if (parsed_text.contains(
"reasoning") &&
676 parsed_text[
"reasoning"].is_string()) {
677 agent_response.reasoning =
678 parsed_text[
"reasoning"].get<std::string>();
682 if (parsed_text.contains(
"commands") &&
683 parsed_text[
"commands"].is_array()) {
684 for (
const auto& cmd : parsed_text[
"commands"]) {
685 if (cmd.is_string()) {
686 std::string command = cmd.get<std::string>();
687 if (absl::StartsWith(command,
"z3ed ")) {
688 command = command.substr(5);
690 agent_response.commands.push_back(command);
696 if (parsed_text.contains(
"tool_calls") &&
697 parsed_text[
"tool_calls"].is_array()) {
698 for (
const auto& call : parsed_text[
"tool_calls"]) {
699 if (call.contains(
"tool_name") && call[
"tool_name"].is_string()) {
701 tool_call.tool_name = call[
"tool_name"].get<std::string>();
703 if (call.contains(
"args") && call[
"args"].is_object()) {
706 agent_response.tool_calls.push_back(tool_call);
713 std::vector<std::string> lines = absl::StrSplit(text_content,
'\n');
714 for (
const auto& line : lines) {
715 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
716 if (!trimmed.empty() && (absl::StartsWith(trimmed,
"z3ed ") ||
717 absl::StartsWith(trimmed,
"palette ") ||
718 absl::StartsWith(trimmed,
"overworld ") ||
719 absl::StartsWith(trimmed,
"sprite ") ||
720 absl::StartsWith(trimmed,
"dungeon "))) {
721 if (absl::StartsWith(trimmed,
"z3ed ")) {
722 trimmed = trimmed.substr(5);
724 agent_response.commands.push_back(trimmed);
728 }
else if (part.contains(
"functionCall")) {
729 const auto& call = part[
"functionCall"];
730 if (call.contains(
"name") && call[
"name"].is_string()) {
732 tool_call.tool_name = call[
"name"].get<std::string>();
733 if (call.contains(
"args") && call[
"args"].is_object()) {
736 agent_response.tool_calls.push_back(tool_call);
742 if (agent_response.text_response.empty() && agent_response.commands.empty() &&
743 agent_response.tool_calls.empty()) {
744 return absl::InternalError(
745 "❌ No valid response extracted from Gemini\n"
746 " Expected at least one of: text_response, commands, or tool_calls\n"
751 return agent_response;
755absl::StatusOr<std::string> GeminiAIService::EncodeImageToBase64(
756 const std::string& image_path)
const {
757#ifndef YAZE_WITH_JSON
759 return absl::UnimplementedError(
760 "Gemini AI service requires JSON support. Build with "
761 "-DYAZE_WITH_JSON=ON");
763 std::ifstream file(image_path, std::ios::binary);
764 if (!file.is_open()) {
765 return absl::NotFoundError(
766 absl::StrCat(
"Failed to open image file: ", image_path));
770 file.seekg(0, std::ios::end);
771 size_t size = file.tellg();
772 file.seekg(0, std::ios::beg);
774 std::vector<unsigned char> buffer(size);
775 if (!file.read(
reinterpret_cast<char*
>(buffer.data()), size)) {
776 return absl::InternalError(
"Failed to read image file");
780 static const char* base64_chars =
781 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
784 encoded.reserve(((size + 2) / 3) * 4);
788 unsigned char char_array_3[3];
789 unsigned char char_array_4[4];
791 for (
size_t idx = 0; idx < size; idx++) {
792 char_array_3[i++] = buffer[idx];
794 char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
796 ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
798 ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
799 char_array_4[3] = char_array_3[2] & 0x3f;
801 for (i = 0; i < 4; i++)
802 encoded += base64_chars[char_array_4[i]];
808 for (j = i; j < 3; j++)
809 char_array_3[j] =
'\0';
811 char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
813 ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
815 ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
817 for (j = 0; j < i + 1; j++)
818 encoded += base64_chars[char_array_4[j]];
829 const std::string& image_path,
const std::string& prompt) {
830#ifndef YAZE_WITH_JSON
833 return absl::UnimplementedError(
834 "Gemini AI service requires JSON support. Build with "
835 "-DYAZE_WITH_JSON=ON");
837 if (config_.api_key.empty()) {
838 return absl::FailedPreconditionError(
"Gemini API key not configured");
842 std::string mime_type =
"image/png";
843 if (image_path.ends_with(
".jpg") || image_path.ends_with(
".jpeg")) {
844 mime_type =
"image/jpeg";
845 }
else if (image_path.ends_with(
".bmp")) {
846 mime_type =
"image/bmp";
847 }
else if (image_path.ends_with(
".webp")) {
848 mime_type =
"image/webp";
852 auto encoded_or = EncodeImageToBase64(image_path);
853 if (!encoded_or.ok()) {
854 return encoded_or.status();
856 std::string encoded_image = std::move(encoded_or.value());
859 if (config_.verbose) {
860 std::cerr <<
"[DEBUG] Preparing multimodal request with image"
865 nlohmann::json request_body = {
869 {{
"mime_type", mime_type}, {
"data", encoded_image}}}},
870 {{
"text", prompt}}}}}}},
872 {{
"temperature", config_.temperature},
873 {
"maxOutputTokens", config_.max_output_tokens}}}};
875 std::string endpoint =
876 "https://generativelanguage.googleapis.com/v1beta/models/" +
877 config_.model +
":generateContent";
878 std::string response_str;
879#if defined(YAZE_AI_IOS_URLSESSION)
880 std::map<std::string, std::string> headers;
881 headers.emplace(
"Content-Type",
"application/json");
882 headers.emplace(
"x-goog-api-key", config_.api_key);
884 request_body.dump(), 60000);
886 return resp_or.status();
888 if (resp_or->status_code != 200) {
889 return absl::InternalError(absl::StrCat(
890 "Gemini API error: ", resp_or->status_code,
"\n", resp_or->body));
892 response_str = resp_or->body;
895 std::string temp_file =
"/tmp/gemini_multimodal_request.json";
896 std::ofstream out(temp_file);
897 out << request_body.dump();
901 std::string curl_cmd =
"curl -s -X POST '" + endpoint +
903 "-H 'Content-Type: application/json' "
904 "-H 'x-goog-api-key: " +
910 if (config_.verbose) {
911 std::cerr <<
"[DEBUG] Executing multimodal API request..." << std::endl;
915 FILE* pipe = _popen(curl_cmd.c_str(),
"r");
917 FILE* pipe = popen(curl_cmd.c_str(),
"r");
920 return absl::InternalError(
"Failed to execute curl command");
924 while (fgets(buffer,
sizeof(buffer), pipe) !=
nullptr) {
925 response_str += buffer;
929 int status = _pclose(pipe);
931 int status = pclose(pipe);
933 std::remove(temp_file.c_str());
936 return absl::InternalError(
937 absl::StrCat(
"Curl failed with status ", status));
941 if (response_str.empty()) {
942 return absl::InternalError(
"Empty response from Gemini API");
945 if (config_.verbose) {
948 <<
"🔍 Raw Gemini Multimodal Response:"
951 <<
"\033[2m" << response_str.substr(0, 500) <<
"\033[0m"
955 return ParseGeminiResponse(response_str);
957 }
catch (
const std::exception& e) {
958 if (config_.verbose) {
959 std::cerr <<
"[ERROR] Exception: " << e.what() << std::endl;
961 return absl::InternalError(
962 absl::StrCat(
"Exception during multimodal generation: ", e.what()));
absl::StatusOr< AgentResponse > GenerateMultimodalResponse(const std::string &, const std::string &)
void EnableFunctionCalling(bool)
absl::Status CheckAvailability()
std::vector< std::string > GetAvailableTools() const
absl::StatusOr< std::vector< ModelInfo > > ListAvailableModels() override
GeminiAIService(const GeminiConfig &)
void SetRomContext(Rom *) override
absl::StatusOr< AgentResponse > GenerateResponse(const std::string &prompt) override
std::map< std::string, std::string > DecodeToolCallArguments(const nlohmann::json &arguments)
absl::StatusOr< nlohmann::json > BuildGeminiToolPayload(const PromptBuilder &prompt_builder)
absl::StatusOr< UrlSessionHttpResponse > UrlSessionHttpRequest(const std::string &method, const std::string &url, const std::map< std::string, std::string > &headers, const std::string &body, int timeout_ms)
constexpr char kProviderGemini[]