299 bool executed_tool =
false;
300 std::vector<std::string> executed_tools;
303 for (
const auto& tool_call : agent_response.
tool_calls) {
305 std::vector<std::string> arg_parts;
306 for (
const auto& [key, value] : tool_call.args) {
307 arg_parts.push_back(absl::StrCat(key,
"=", value));
309 std::string args_str = absl::StrJoin(arg_parts,
", ");
314 std::string tool_output;
315 if (!tool_result_or.ok()) {
316 tool_output = absl::StrCat(
"Error: ", tool_result_or.status().message());
319 tool_output = tool_result_or.value();
323 if (!tool_output.empty()) {
326 std::string marked_output = absl::StrCat(
327 "[TOOL RESULT for ", tool_call.tool_name,
"]\n",
328 "The tool returned the following data:\n", tool_output,
"\n\n",
329 "Please provide a text_response field in your JSON to summarize "
330 "this information for the user.");
331 auto tool_result_msg =
333 tool_result_msg.is_internal =
true;
334 history_.push_back(tool_result_msg);
336 executed_tool =
true;
337 executed_tools.push_back(tool_call.tool_name);
348 std::optional<ProposalCreationResult> proposal_result;
349 absl::Status proposal_status = absl::OkStatus();
350 bool attempted_proposal =
false;
352 if (!agent_response.
commands.empty()) {
353 attempted_proposal =
true;
360 request.
prompt = it->message;
371 if (!creation_or.ok()) {
372 proposal_status = creation_or.status();
374 proposal_status.message()));
376 proposal_result = std::move(creation_or.value());
384 if (!response_text.empty()) response_text.append(
"\n\n");
385 response_text.append(
"Reasoning: ").append(agent_response.
reasoning);
388 if (!agent_response.
commands.empty()) {
389 if (!response_text.empty()) response_text.append(
"\n\n");
390 response_text.append(
"Commands:\n").append(absl::StrJoin(agent_response.
commands,
"\n"));
394 if (proposal_result.has_value()) {
395 const auto& metadata = proposal_result->metadata;
396 if (!response_text.empty()) response_text.append(
"\n\n");
397 response_text.append(absl::StrFormat(
398 "✅ Proposal %s ready with %d change%s (%d command%s).",
399 metadata.id, proposal_result->change_count,
400 proposal_result->change_count == 1 ?
"" :
"s",
401 proposal_result->executed_commands,
402 proposal_result->executed_commands == 1 ?
"" :
"s"));
404 }
else if (attempted_proposal && !proposal_status.ok()) {
405 if (!response_text.empty()) response_text.append(
"\n\n");
406 response_text.append(absl::StrCat(
"⚠️ Failed to prepare proposal: ", proposal_status.message()));
411 history_.back().message ==
"Thinking...") {
417 if (proposal_result.has_value()) {
419 summary.
id = proposal_result->metadata.id;
428 meta.
model =
"gemini";
440 const std::string& message) {
441 if (message.empty() &&
history_.empty()) {
442 return absl::InvalidArgumentError(
443 "Conversation must start with a non-empty message.");
446 if (!message.empty()) {
461 bool waiting_for_text_response =
false;
462 absl::Time turn_start = absl::Now();
463 std::vector<std::string> executed_tools;
466 util::PrintInfo(absl::StrCat(
"Starting agent loop (max ", max_iterations,
469 absl::StrCat(
"History size: ",
history_.size(),
" messages"));
472 for (
int iteration = 0; iteration < max_iterations; ++iteration) {
481 waiting_for_text_response ?
"Generating final response..."
489 if (!response_or.ok()) {
491 response_or.status().message()));
492 return absl::InternalError(absl::StrCat(
"Failed to get AI response: ",
493 response_or.status().message()));
496 const auto& agent_response = response_or.value();
501 <<
" - Tool calls: " << agent_response.tool_calls.size()
504 <<
" - Commands: " << agent_response.commands.size()
507 << (agent_response.text_response.empty() ?
"empty" :
"present")
517 if (!agent_response.tool_calls.empty()) {
520 if (waiting_for_text_response) {
522 absl::StrCat(
"LLM called tools again instead of providing final "
523 "response (Iteration: ",
524 iteration + 1,
"/", max_iterations,
")"));
527 bool executed_tool =
false;
528 for (
const auto& tool_call : agent_response.tool_calls) {
530 std::vector<std::string> arg_parts;
531 for (
const auto& [key, value] : tool_call.args) {
532 arg_parts.push_back(absl::StrCat(key,
"=", value));
534 std::string args_str = absl::StrJoin(arg_parts,
", ");
539 if (!tool_result_or.ok()) {
541 tool_result_or.status().message()));
542 return absl::InternalError(absl::StrCat(
543 "Tool execution failed: ", tool_result_or.status().message()));
546 const std::string& tool_output = tool_result_or.value();
547 if (!tool_output.empty()) {
555 std::string preview = tool_output.substr(
556 0, std::min(
size_t(200), tool_output.size()));
557 if (tool_output.size() > 200)
565 std::string marked_output = absl::StrCat(
566 "[TOOL RESULT for ", tool_call.tool_name,
"]\n",
567 "The tool returned the following data:\n", tool_output,
"\n\n",
568 "Please provide a text_response field in your JSON to summarize "
569 "this information for the user.");
570 auto tool_result_msg =
572 tool_result_msg.is_internal =
574 history_.push_back(tool_result_msg);
576 executed_tool =
true;
577 executed_tools.push_back(tool_call.tool_name);
582 waiting_for_text_response =
true;
589 if (waiting_for_text_response && agent_response.text_response.empty() &&
590 agent_response.commands.empty()) {
592 absl::StrCat(
"LLM did not provide text_response after receiving tool "
593 "results (Iteration: ",
594 iteration + 1,
"/", max_iterations,
")"));
599 std::optional<ProposalCreationResult> proposal_result;
600 absl::Status proposal_status = absl::OkStatus();
601 bool attempted_proposal =
false;
603 if (!agent_response.commands.empty()) {
604 attempted_proposal =
true;
607 proposal_status = absl::FailedPreconditionError(
608 "No ROM context available for proposal creation");
610 "Cannot create proposal because no ROM context is active.");
613 absl::FailedPreconditionError(
"ROM context is not loaded");
615 "Cannot create proposal because the ROM context is not loaded.");
622 request.
ai_provider = absl::GetFlag(FLAGS_ai_provider);
625 if (!creation_or.ok()) {
626 proposal_status = creation_or.status();
628 proposal_status.message()));
630 proposal_result = std::move(creation_or.value());
633 "Created proposal ", proposal_result->metadata.id,
" with ",
634 proposal_result->change_count,
" change(s)."));
640 std::string response_text = agent_response.text_response;
641 if (!agent_response.reasoning.empty()) {
642 if (!response_text.empty()) {
643 response_text.append(
"\n\n");
645 response_text.append(
"Reasoning: ");
646 response_text.append(agent_response.reasoning);
648 const int executable_commands =
649 CountExecutableCommands(agent_response.commands);
650 if (!agent_response.commands.empty()) {
651 if (!response_text.empty()) {
652 response_text.append(
"\n\n");
654 response_text.append(
"Commands:\n");
655 response_text.append(absl::StrJoin(agent_response.commands,
"\n"));
659 if (proposal_result.has_value()) {
660 const auto& metadata = proposal_result->metadata;
661 if (!response_text.empty()) {
662 response_text.append(
"\n\n");
664 response_text.append(absl::StrFormat(
665 "✅ Proposal %s ready with %d change%s (%d command%s).\n"
666 "Review it in the Proposal drawer or run `z3ed agent diff "
667 "--proposal-id %s`.\n"
668 "Sandbox ROM: %s\nProposal JSON: %s",
669 metadata.id, proposal_result->change_count,
670 proposal_result->change_count == 1 ?
"" :
"s",
671 proposal_result->executed_commands,
672 proposal_result->executed_commands == 1 ?
"" :
"s", metadata.id,
673 metadata.sandbox_rom_path.string(),
674 proposal_result->proposal_json_path.string()));
676 }
else if (attempted_proposal && !proposal_status.ok()) {
677 if (!response_text.empty()) {
678 response_text.append(
"\n\n");
680 response_text.append(
681 absl::StrCat(
"⚠️ Failed to prepare a proposal automatically: ",
682 proposal_status.message()));
686 if (proposal_result.has_value()) {
688 summary.
id = proposal_result->metadata.id;
699 if (!agent_response.warnings.empty()) {
700 chat_response.
warnings = agent_response.warnings;
703 meta.
provider = !agent_response.provider.empty()
704 ? agent_response.provider
706 meta.
model = !agent_response.model.empty() ? agent_response.model
709 agent_response.latency_seconds > 0.0
710 ? agent_response.latency_seconds
711 : absl::ToDoubleSeconds(absl::Now() - turn_start);
718 return chat_response;
721 return absl::InternalError(
722 "Agent did not produce a response after executing tools.");