feat: Add simple chat session for AI agent interaction and enhance message rendering

This commit is contained in:
scawful
2025-10-03 23:55:08 -04:00
parent a6cdc651c3
commit 94cf867d36
8 changed files with 294 additions and 81 deletions

View File

@@ -12,7 +12,7 @@ namespace agent {
namespace {
constexpr absl::string_view kUsage =
"Usage: agent <run|plan|diff|accept|test|test-conversation|gui|learn|list|commit|revert|describe|resource-list|dungeon-list-sprites|overworld-find-tile|overworld-describe-map|overworld-list-warps|chat> "
"Usage: agent <run|plan|diff|accept|test|test-conversation|gui|learn|list|commit|revert|describe|resource-list|dungeon-list-sprites|overworld-find-tile|overworld-describe-map|overworld-list-warps|chat|simple-chat> "
"[options]";
} // namespace
@@ -80,6 +80,9 @@ absl::Status Agent::Run(const std::vector<std::string>& arg_vec) {
if (subcommand == "chat") {
return agent::HandleChatCommand(rom_);
}
if (subcommand == "simple-chat") {
return agent::HandleSimpleChatCommand(subcommand_args, rom_);
}
return absl::InvalidArgumentError(std::string(agent::kUsage));
}

View File

@@ -41,6 +41,7 @@ absl::Status HandleOverworldListWarpsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleChatCommand(Rom& rom);
absl::Status HandleSimpleChatCommand(const std::vector<std::string>& arg_vec, Rom& rom);
absl::Status HandleTestConversationCommand(
const std::vector<std::string>& arg_vec);

View File

@@ -26,6 +26,7 @@
#include "cli/service/ai/gemini_ai_service.h"
#include "cli/service/ai/ollama_ai_service.h"
#include "cli/service/ai/service_factory.h"
#include "cli/service/agent/simple_chat_session.h"
#include "cli/service/planning/proposal_registry.h"
#include "cli/service/planning/tile16_proposal_generator.h"
#include "cli/service/resources/resource_catalog.h"
@@ -571,6 +572,32 @@ absl::Status HandleChatCommand(Rom& rom) {
return absl::OkStatus();
}
absl::Status HandleSimpleChatCommand(const std::vector<std::string>& arg_vec,
Rom& rom) {
RETURN_IF_ERROR(EnsureRomLoaded(rom, "agent simple-chat"));
// Parse flags
std::optional<std::string> batch_file;
for (size_t i = 0; i < arg_vec.size(); ++i) {
const std::string& arg = arg_vec[i];
if (absl::StartsWith(arg, "--file=")) {
batch_file = arg.substr(7);
} else if (arg == "--file" && i + 1 < arg_vec.size()) {
batch_file = arg_vec[i + 1];
++i;
}
}
SimpleChatSession session;
session.SetRomContext(&rom);
if (batch_file.has_value()) {
return session.RunBatch(*batch_file);
} else {
return session.RunInteractive();
}
}
absl::Status HandleAcceptCommand(const std::vector<std::string>& arg_vec,
Rom& rom) {
std::optional<std::string> proposal_id;

View File

@@ -0,0 +1,160 @@
#include "cli/service/agent/simple_chat_session.h"
#include <fstream>
#include <iostream>
#include <iomanip>
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
namespace yaze {
namespace cli {
namespace agent {
SimpleChatSession::SimpleChatSession() = default;
void SimpleChatSession::SetRomContext(Rom* rom) {
agent_service_.SetRomContext(rom);
}
void SimpleChatSession::PrintTable(const ChatMessage::TableData& table) {
if (table.headers.empty()) return;
// Calculate column widths
std::vector<size_t> col_widths(table.headers.size(), 0);
for (size_t i = 0; i < table.headers.size(); ++i) {
col_widths[i] = table.headers[i].length();
}
for (const auto& row : table.rows) {
for (size_t i = 0; i < std::min(row.size(), col_widths.size()); ++i) {
col_widths[i] = std::max(col_widths[i], row[i].length());
}
}
// Print header
std::cout << " ";
for (size_t i = 0; i < table.headers.size(); ++i) {
std::cout << std::left << std::setw(col_widths[i] + 2) << table.headers[i];
}
std::cout << "\n ";
for (size_t i = 0; i < table.headers.size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
}
std::cout << "\n";
// Print rows
for (const auto& row : table.rows) {
std::cout << " ";
for (size_t i = 0; i < std::min(row.size(), table.headers.size()); ++i) {
std::cout << std::left << std::setw(col_widths[i] + 2) << row[i];
}
std::cout << "\n";
}
}
void SimpleChatSession::PrintMessage(const ChatMessage& msg, bool show_timestamp) {
const char* sender = (msg.sender == ChatMessage::Sender::kUser) ? "You" : "Agent";
if (show_timestamp) {
std::string timestamp = absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone());
std::cout << "[" << timestamp << "] ";
}
std::cout << sender << ": ";
if (msg.table_data.has_value()) {
std::cout << "\n";
PrintTable(msg.table_data.value());
} else if (msg.json_pretty.has_value()) {
std::cout << "\n" << msg.json_pretty.value() << "\n";
} else {
std::cout << msg.message << "\n";
}
}
absl::Status SimpleChatSession::SendAndWaitForResponse(
const std::string& message, std::string* response_out) {
auto result = agent_service_.SendMessage(message);
if (!result.ok()) {
return result.status();
}
const auto& response_msg = result.value();
if (response_out != nullptr) {
*response_out = response_msg.message;
}
return absl::OkStatus();
}
absl::Status SimpleChatSession::RunInteractive() {
std::cout << "Z3ED Agent Chat (Simple Mode)\n";
std::cout << "Type 'quit' or 'exit' to end the session.\n";
std::cout << "Type 'reset' to clear conversation history.\n";
std::cout << "----------------------------------------\n\n";
std::string input;
while (true) {
std::cout << "You: ";
std::getline(std::cin, input);
if (input.empty()) continue;
if (input == "quit" || input == "exit") break;
if (input == "reset") {
Reset();
std::cout << "Conversation history cleared.\n\n";
continue;
}
auto result = agent_service_.SendMessage(input);
if (!result.ok()) {
std::cerr << "Error: " << result.status().message() << "\n\n";
continue;
}
PrintMessage(result.value(), false);
std::cout << "\n";
}
return absl::OkStatus();
}
absl::Status SimpleChatSession::RunBatch(const std::string& input_file) {
std::ifstream file(input_file);
if (!file.is_open()) {
return absl::NotFoundError(
absl::StrFormat("Could not open file: %s", input_file));
}
std::cout << "Running batch session from: " << input_file << "\n";
std::cout << "----------------------------------------\n\n";
std::string line;
int line_num = 0;
while (std::getline(file, line)) {
++line_num;
// Skip empty lines and comments
if (line.empty() || line[0] == '#') continue;
std::cout << "Input [" << line_num << "]: " << line << "\n";
auto result = agent_service_.SendMessage(line);
if (!result.ok()) {
std::cerr << "Error: " << result.status().message() << "\n\n";
continue;
}
PrintMessage(result.value(), false);
std::cout << "\n";
}
return absl::OkStatus();
}
} // namespace agent
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,62 @@
#ifndef YAZE_SRC_CLI_SERVICE_AGENT_SIMPLE_CHAT_SESSION_H_
#define YAZE_SRC_CLI_SERVICE_AGENT_SIMPLE_CHAT_SESSION_H_
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "cli/service/agent/conversational_agent_service.h"
namespace yaze {
class Rom;
namespace cli {
namespace agent {
/**
* @class SimpleChatSession
* @brief Simple text-based chat session for AI agent interaction
*
* Provides a basic REPL-style interface without FTXUI dependencies,
* suitable for automated testing and AI agent interactions.
*/
class SimpleChatSession {
public:
SimpleChatSession();
// Set ROM context for tool execution
void SetRomContext(Rom* rom);
// Send a single message and get response (blocking)
absl::Status SendAndWaitForResponse(const std::string& message,
std::string* response_out = nullptr);
// Run interactive REPL mode (reads from stdin)
absl::Status RunInteractive();
// Run batch mode from file (one message per line)
absl::Status RunBatch(const std::string& input_file);
// Get full conversation history
const std::vector<ChatMessage>& GetHistory() const {
return agent_service_.GetHistory();
}
// Clear conversation history
void Reset() {
agent_service_.ResetConversation();
}
private:
void PrintMessage(const ChatMessage& msg, bool show_timestamp = false);
void PrintTable(const ChatMessage::TableData& table);
ConversationalAgentService agent_service_;
};
} // namespace agent
} // namespace cli
} // namespace yaze
#endif // YAZE_SRC_CLI_SERVICE_AGENT_SIMPLE_CHAT_SESSION_H_