17#include "absl/strings/str_cat.h"
18#include "absl/strings/str_format.h"
19#include "absl/strings/str_join.h"
20#include "absl/time/time.h"
35std::string EscapeJsonString(absl::string_view input) {
37 output.reserve(input.size());
38 for (
char ch : input) {
41 output.append(
"\\\\");
44 output.append(
"\\\"");
62 unsigned char code =
static_cast<unsigned char>(ch);
64 absl::StrAppendFormat(&output,
"\\u%04x",
65 static_cast<unsigned int>(code));
77 return absl::StrCat(
"\"", EscapeJsonString(value),
"\"");
81 std::vector<std::string> header_entries;
82 header_entries.reserve(table.
headers.size());
83 for (
const auto& header : table.
headers) {
84 header_entries.push_back(
QuoteJson(header));
87 std::vector<std::string> row_entries;
88 row_entries.reserve(table.
rows.size());
89 for (
const auto& row : table.
rows) {
90 std::vector<std::string> cell_entries;
91 cell_entries.reserve(row.size());
92 for (
const auto& cell : row) {
95 row_entries.push_back(
96 absl::StrCat(
"[", absl::StrJoin(cell_entries,
","),
"]"));
99 return absl::StrCat(
"{\"headers\":[", absl::StrJoin(header_entries,
","),
100 "],\"rows\":[", absl::StrJoin(row_entries,
","),
"]}");
104 return absl::StrCat(
"{\"turn_index\":", metrics.
turn_index,
106 "\"total_user_messages\":",
109 "\"total_agent_messages\":",
112 "\"total_tool_calls\":",
115 "\"total_commands\":",
118 "\"total_proposals\":",
121 "\"total_elapsed_seconds\":",
124 "\"average_latency_seconds\":",
129 std::string
json =
"{";
130 absl::StrAppend(&
json,
"\"sender\":\"",
137 absl::StrAppend(&
json,
149 if (show_timestamp) {
150 std::string timestamp = absl::FormatTime(
151 "%Y-%m-%dT%H:%M:%S%z", msg.
timestamp, absl::LocalTimeZone());
152 absl::StrAppend(&
json,
",\"timestamp\":",
QuoteJson(timestamp));
155 absl::StrAppend(&
json,
"}");
165 for (
const auto& header : table.
headers) {
166 std::cout <<
" " << header <<
" |";
169 for (
size_t i = 0; i < table.
headers.size(); ++i) {
170 std::cout <<
" --- |";
174 for (
const auto& row : table.
rows) {
176 for (
size_t i = 0; i < table.
headers.size(); ++i) {
177 if (i < row.size()) {
178 std::cout <<
" " << row[i];
187 std::cout <<
"\n> _Turn " << metrics.
turn_index
209 std::vector<size_t> col_widths(table.
headers.size(), 0);
210 for (
size_t i = 0; i < table.
headers.size(); ++i) {
211 col_widths[i] = table.
headers[i].length();
214 for (
const auto& row : table.
rows) {
215 for (
size_t i = 0; i < std::min(row.size(), col_widths.size()); ++i) {
216 col_widths[i] = std::max(col_widths[i], row[i].length());
222 for (
size_t i = 0; i < table.
headers.size(); ++i) {
223 std::cout << std::left << std::setw(col_widths[i] + 2) << table.
headers[i];
226 for (
size_t i = 0; i < table.
headers.size(); ++i) {
227 std::cout << std::string(col_widths[i] + 2,
'-');
232 for (
const auto& row : table.
rows) {
234 for (
size_t i = 0; i < std::min(row.size(), table.
headers.size()); ++i) {
235 std::cout << std::left << std::setw(col_widths[i] + 2) << row[i];
242 bool show_timestamp) {
248 if (show_timestamp) {
249 std::string timestamp =
250 absl::FormatTime(
"%H:%M:%S", msg.
timestamp, absl::LocalTimeZone());
251 std::cout <<
"[" << timestamp <<
"] ";
254 std::cout << sender <<
": ";
260 std::cout <<
"\n" << msg.
json_pretty.value() <<
"\n";
262 std::cout << msg.
message <<
"\n";
266 const auto& metrics = msg.
metrics.value();
267 std::cout <<
" 📊 Turn " << metrics.turn_index
268 <<
" summary — users: " << metrics.total_user_messages
269 <<
", agents: " << metrics.total_agent_messages
270 <<
", tools: " << metrics.total_tool_calls
271 <<
", commands: " << metrics.total_commands
272 <<
", proposals: " << metrics.total_proposals <<
", elapsed: "
273 << absl::StrFormat(
"%.2fs avg %.2fs",
274 metrics.total_elapsed_seconds,
275 metrics.average_latency_seconds)
286 std::cout << msg.
message <<
"\n";
296 std::cout <<
"\n```json\n" << msg.
json_pretty.value() <<
"\n```\n";
298 std::cout << msg.
message <<
"\n";
302 PrintMarkdownMetrics(*msg.
metrics);
307 std::cout << MessageToJson(msg, show_timestamp) << std::endl;
314 const std::string& message, std::string* response_out) {
317 return result.status();
320 const auto& response_msg = result.value();
322 if (response_out !=
nullptr) {
323 *response_out = response_msg.message;
326 return absl::OkStatus();
331 bool is_interactive = isatty(fileno(stdin));
334 std::cout <<
"Z3ED Agent Chat (Simple Mode)\n";
336 std::cout <<
"Vim mode enabled! Use hjkl to move, i for insert, ESC for "
339 std::cout <<
"Type 'quit' or 'exit' to end the session.\n";
340 std::cout <<
"Type 'reset' to clear conversation history.\n";
341 std::cout <<
"----------------------------------------\n\n";
358 std::cout <<
"You: ";
362 if (!std::getline(std::cin, input)) {
364 if (is_interactive &&
374 if (input ==
"quit" || input ==
"exit")
377 if (input ==
"reset") {
380 std::cout <<
"{\"event\":\"history_cleared\"}" << std::endl;
382 std::cout <<
"> Conversation history cleared.\n\n";
384 std::cout <<
"Conversation history cleared.\n\n";
392 std::cout << absl::StrCat(
"{\"event\":\"error\",\"message\":",
393 QuoteJson(result.status().message()),
"}")
396 std::cout <<
"> **Error:** " << result.status().message() <<
"\n\n";
398 std::cout <<
"error: " << result.status().message() <<
"\n";
400 std::cerr <<
"Error: " << result.status().message() <<
"\n\n";
413 std::cout << absl::StrCat(
"{\"event\":\"session_summary\",\"metrics\":",
414 SessionMetricsToJson(metrics),
"}")
417 std::cout <<
"\n> **Session totals** ";
419 <<
", users=" << metrics.total_user_messages
420 <<
", agents=" << metrics.total_agent_messages
421 <<
", tools=" << metrics.total_tool_calls
422 <<
", commands=" << metrics.total_commands
423 <<
", proposals=" << metrics.total_proposals <<
", elapsed="
424 << absl::StrFormat(
"%.2fs avg %.2fs",
425 metrics.total_elapsed_seconds,
426 metrics.average_latency_seconds)
429 std::cout <<
"Session totals — turns: " << metrics.turn_index
430 <<
", user messages: " << metrics.total_user_messages
431 <<
", agent messages: " << metrics.total_agent_messages
432 <<
", tool calls: " << metrics.total_tool_calls
433 <<
", commands: " << metrics.total_commands
434 <<
", proposals: " << metrics.total_proposals <<
", elapsed: "
435 << absl::StrFormat(
"%.2fs avg %.2fs\n\n",
436 metrics.total_elapsed_seconds,
437 metrics.average_latency_seconds);
440 return absl::OkStatus();
444 std::ifstream file(input_file);
445 if (!file.is_open()) {
446 return absl::NotFoundError(
447 absl::StrFormat(
"Could not open file: %s", input_file));
451 std::cout <<
"Running batch session from: " << input_file <<
"\n";
452 std::cout <<
"----------------------------------------\n\n";
454 std::cout <<
"### Batch session: " << input_file <<
"\n\n";
459 while (std::getline(file, line)) {
463 if (line.empty() || line[0] ==
'#')
467 std::cout <<
"Input [" << line_num <<
"]: " << line <<
"\n";
469 std::cout <<
"- **Input " << line_num <<
"**: " << line <<
"\n";
471 std::cout << absl::StrCat(
"{\"event\":\"batch_input\",\"index\":",
472 line_num,
",\"prompt\":", QuoteJson(line),
"}")
479 std::cout << absl::StrCat(
"{\"event\":\"error\",\"index\":", line_num,
481 QuoteJson(result.status().message()),
"}")
484 std::cout <<
" - ⚠️ " << result.status().message() <<
"\n";
486 std::cout <<
"error@" << line_num <<
": " << result.status().message()
489 std::cerr <<
"Error: " << result.status().message() <<
"\n\n";
502 std::cout << absl::StrCat(
"{\"event\":\"session_summary\",\"metrics\":",
503 SessionMetricsToJson(metrics),
"}")
506 std::cout <<
"\n> **Batch totals** turns=" << metrics.
turn_index
507 <<
", users=" << metrics.total_user_messages
508 <<
", agents=" << metrics.total_agent_messages
509 <<
", tools=" << metrics.total_tool_calls
510 <<
", commands=" << metrics.total_commands
511 <<
", proposals=" << metrics.total_proposals <<
", elapsed="
512 << absl::StrFormat(
"%.2fs avg %.2fs",
513 metrics.total_elapsed_seconds,
514 metrics.average_latency_seconds)
517 std::cout <<
"Batch session totals — turns: " << metrics.turn_index
518 <<
", user messages: " << metrics.total_user_messages
519 <<
", agent messages: " << metrics.total_agent_messages
520 <<
", tool calls: " << metrics.total_tool_calls
521 <<
", commands: " << metrics.total_commands
522 <<
", proposals: " << metrics.total_proposals <<
", elapsed: "
523 << absl::StrFormat(
"%.2fs avg %.2fs\n\n",
524 metrics.total_elapsed_seconds,
525 metrics.average_latency_seconds);
528 return absl::OkStatus();
534 vim_mode_->SetAutoCompleteCallback([
this](
const std::string& partial) {
542 std::cout <<
"You [" <<
vim_mode_->GetModeString() <<
"]: " << std::flush;
550 if (read(STDIN_FILENO, &c, 1) == 1) {
551 ch =
static_cast<int>(c);
570 const std::string& partial) {
572 std::vector<std::string> all_commands = {
573 "/help",
"/exit",
"/quit",
"/reset",
574 "/history",
"list rooms",
"list sprites",
"list palettes",
575 "show room ",
"describe ",
"analyze "};
577 std::vector<std::string> matches;
578 for (
const auto& cmd : all_commands) {
579 if (cmd.find(partial) == 0) {
580 matches.push_back(cmd);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< ChatMessage > SendMessage(const std::string &message)
void SetRomContext(Rom *rom)
ChatMessage::SessionMetrics GetMetrics() const
void SetRomContext(Rom *rom)
std::vector< std::string > GetAutocompleteOptions(const std::string &partial)
void PrintTable(const ChatMessage::TableData &table)
absl::Status RunBatch(const std::string &input_file)
void PrintMessage(const ChatMessage &msg, bool show_timestamp=false)
ConversationalAgentService agent_service_
absl::Status RunInteractive()
std::unique_ptr< VimMode > vim_mode_
std::string ReadLineWithVim()
absl::Status SendAndWaitForResponse(const std::string &message, std::string *response_out=nullptr)
std::string QuoteJson(absl::string_view value)
std::string TableToJson(const ChatMessage::TableData &table)
void PrintMarkdownMetrics(const ChatMessage::SessionMetrics &metrics)
std::string SessionMetricsToJson(const ChatMessage::SessionMetrics &metrics)
std::string MetricsToJson(const ChatMessage::SessionMetrics &metrics)
void PrintMarkdownTable(const ChatMessage::TableData &table)
std::string MessageToJson(const ChatMessage &msg, bool show_timestamp)
AgentOutputFormat output_format
double total_elapsed_seconds
double average_latency_seconds
std::vector< std::string > headers
std::vector< std::vector< std::string > > rows
std::optional< TableData > table_data
std::optional< std::string > json_pretty
std::optional< SessionMetrics > metrics