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,
","),
117 std::string json =
"{";
118 absl::StrAppend(&json,
"\"sender\":\"",
126 absl::StrAppend(&json,
",\"structured\":",
135 absl::StrAppend(&json,
",\"metrics\":",
139 if (show_timestamp) {
140 std::string timestamp =
141 absl::FormatTime(
"%Y-%m-%dT%H:%M:%S%z", msg.
timestamp,
142 absl::LocalTimeZone());
143 absl::StrAppend(&json,
",\"timestamp\":",
QuoteJson(timestamp));
146 absl::StrAppend(&json,
"}");
156 for (
const auto& header : table.
headers) {
157 std::cout <<
" " << header <<
" |";
160 for (
size_t i = 0; i < table.
headers.size(); ++i) {
161 std::cout <<
" --- |";
165 for (
const auto& row : table.
rows) {
167 for (
size_t i = 0; i < table.
headers.size(); ++i) {
168 if (i < row.size()) {
169 std::cout <<
" " << row[i];
178 std::cout <<
"\n> _Turn " << metrics.
turn_index
185 << absl::StrFormat(
"%.2fs avg %.2fs",
198 if (table.
headers.empty())
return;
201 std::vector<size_t> col_widths(table.
headers.size(), 0);
202 for (
size_t i = 0; i < table.
headers.size(); ++i) {
203 col_widths[i] = table.
headers[i].length();
206 for (
const auto& row : table.
rows) {
207 for (
size_t i = 0; i < std::min(row.size(), col_widths.size()); ++i) {
208 col_widths[i] = std::max(col_widths[i], row[i].length());
214 for (
size_t i = 0; i < table.
headers.size(); ++i) {
215 std::cout << std::left << std::setw(col_widths[i] + 2) << table.
headers[i];
218 for (
size_t i = 0; i < table.
headers.size(); ++i) {
219 std::cout << std::string(col_widths[i] + 2,
'-');
224 for (
const auto& row : table.
rows) {
226 for (
size_t i = 0; i < std::min(row.size(), table.
headers.size()); ++i) {
227 std::cout << std::left << std::setw(col_widths[i] + 2) << row[i];
234 bool show_timestamp) {
240 if (show_timestamp) {
241 std::string timestamp = absl::FormatTime(
242 "%H:%M:%S", msg.
timestamp, absl::LocalTimeZone());
243 std::cout <<
"[" << timestamp <<
"] ";
246 std::cout << sender <<
": ";
252 std::cout <<
"\n" << msg.
json_pretty.value() <<
"\n";
254 std::cout << msg.
message <<
"\n";
258 const auto& metrics = msg.
metrics.value();
259 std::cout <<
" 📊 Turn " << metrics.turn_index
260 <<
" summary — users: " << metrics.total_user_messages
261 <<
", agents: " << metrics.total_agent_messages
262 <<
", tools: " << metrics.total_tool_calls
263 <<
", commands: " << metrics.total_commands
264 <<
", proposals: " << metrics.total_proposals
266 << absl::StrFormat(
"%.2fs avg %.2fs",
267 metrics.total_elapsed_seconds,
268 metrics.average_latency_seconds)
279 std::cout << msg.
message <<
"\n";
289 std::cout <<
"\n```json\n" << msg.
json_pretty.value()
292 std::cout << msg.
message <<
"\n";
296 PrintMarkdownMetrics(*msg.
metrics);
301 std::cout << MessageToJson(msg, show_timestamp) << std::endl;
308 const std::string& message, std::string* response_out) {
311 return result.status();
314 const auto& response_msg = result.value();
316 if (response_out !=
nullptr) {
317 *response_out = response_msg.message;
320 return absl::OkStatus();
325 bool is_interactive = isatty(fileno(stdin));
328 std::cout <<
"Z3ED Agent Chat (Simple Mode)\n";
330 std::cout <<
"Vim mode enabled! Use hjkl to move, i for insert, ESC for normal mode.\n";
332 std::cout <<
"Type 'quit' or 'exit' to end the session.\n";
333 std::cout <<
"Type 'reset' to clear conversation history.\n";
334 std::cout <<
"----------------------------------------\n\n";
351 std::cout <<
"You: ";
355 if (!std::getline(std::cin, input)) {
364 if (input.empty())
continue;
365 if (input ==
"quit" || input ==
"exit")
break;
367 if (input ==
"reset") {
370 std::cout <<
"{\"event\":\"history_cleared\"}" << std::endl;
372 std::cout <<
"> Conversation history cleared.\n\n";
374 std::cout <<
"Conversation history cleared.\n\n";
382 std::cout << absl::StrCat(
383 "{\"event\":\"error\",\"message\":",
384 QuoteJson(result.status().message()),
"}")
387 std::cout <<
"> **Error:** " << result.status().message() <<
"\n\n";
389 std::cout <<
"error: " << result.status().message() <<
"\n";
391 std::cerr <<
"Error: " << result.status().message() <<
"\n\n";
404 std::cout << absl::StrCat(
"{\"event\":\"session_summary\",\"metrics\":",
405 SessionMetricsToJson(metrics),
"}")
408 std::cout <<
"\n> **Session totals** ";
409 std::cout <<
"turns=" << metrics.
turn_index <<
", users="
410 << metrics.total_user_messages <<
", agents="
411 << metrics.total_agent_messages <<
", tools="
412 << metrics.total_tool_calls <<
", commands="
413 << metrics.total_commands <<
", proposals="
414 << metrics.total_proposals <<
", elapsed="
415 << absl::StrFormat(
"%.2fs avg %.2fs",
416 metrics.total_elapsed_seconds,
417 metrics.average_latency_seconds)
420 std::cout <<
"Session totals — turns: " << metrics.turn_index
421 <<
", user messages: " << metrics.total_user_messages
422 <<
", agent messages: " << metrics.total_agent_messages
423 <<
", tool calls: " << metrics.total_tool_calls
424 <<
", commands: " << metrics.total_commands
425 <<
", proposals: " << metrics.total_proposals
427 << absl::StrFormat(
"%.2fs avg %.2fs\n\n",
428 metrics.total_elapsed_seconds,
429 metrics.average_latency_seconds);
432 return absl::OkStatus();
436 std::ifstream file(input_file);
437 if (!file.is_open()) {
438 return absl::NotFoundError(
439 absl::StrFormat(
"Could not open file: %s", input_file));
443 std::cout <<
"Running batch session from: " << input_file <<
"\n";
444 std::cout <<
"----------------------------------------\n\n";
446 std::cout <<
"### Batch session: " << input_file <<
"\n\n";
451 while (std::getline(file, line)) {
455 if (line.empty() || line[0] ==
'#')
continue;
458 std::cout <<
"Input [" << line_num <<
"]: " << line <<
"\n";
460 std::cout <<
"- **Input " << line_num <<
"**: " << line <<
"\n";
462 std::cout << absl::StrCat(
463 "{\"event\":\"batch_input\",\"index\":",
464 line_num,
",\"prompt\":", QuoteJson(line),
"}")
471 std::cout << absl::StrCat(
472 "{\"event\":\"error\",\"index\":", line_num,
474 QuoteJson(result.status().message()),
"}")
477 std::cout <<
" - ⚠️ " << result.status().message() <<
"\n";
479 std::cout <<
"error@" << line_num <<
": "
480 << result.status().message() <<
"\n";
482 std::cerr <<
"Error: " << result.status().message() <<
"\n\n";
495 std::cout << absl::StrCat(
"{\"event\":\"session_summary\",\"metrics\":",
496 SessionMetricsToJson(metrics),
"}")
499 std::cout <<
"\n> **Batch totals** turns=" << metrics.
turn_index
500 <<
", users=" << metrics.total_user_messages <<
", agents="
501 << metrics.total_agent_messages <<
", tools="
502 << metrics.total_tool_calls <<
", commands="
503 << metrics.total_commands <<
", proposals="
504 << metrics.total_proposals <<
", elapsed="
505 << absl::StrFormat(
"%.2fs avg %.2fs",
506 metrics.total_elapsed_seconds,
507 metrics.average_latency_seconds)
510 std::cout <<
"Batch session totals — turns: " << metrics.turn_index
511 <<
", user messages: " << metrics.total_user_messages
512 <<
", agent messages: " << metrics.total_agent_messages
513 <<
", tool calls: " << metrics.total_tool_calls
514 <<
", commands: " << metrics.total_commands
515 <<
", proposals: " << metrics.total_proposals
517 << absl::StrFormat(
"%.2fs avg %.2fs\n\n",
518 metrics.total_elapsed_seconds,
519 metrics.average_latency_seconds);
522 return absl::OkStatus();
529 [
this](
const std::string& partial) {
537 std::cout <<
"You [" <<
vim_mode_->GetModeString() <<
"]: " << std::flush;
545 if (read(STDIN_FILENO, &c, 1) == 1) {
546 ch =
static_cast<int>(c);
565 const std::string& partial) {
567 std::vector<std::string> all_commands = {
568 "/help",
"/exit",
"/quit",
"/reset",
"/history",
569 "list rooms",
"list sprites",
"list palettes",
570 "show room ",
"describe ",
"analyze "
573 std::vector<std::string> matches;
574 for (
const auto& cmd : all_commands) {
575 if (cmd.find(partial) == 0) {
576 matches.push_back(cmd);
The Rom class is used to load, save, and modify Rom data.
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)
Main namespace for the application.
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