yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
simple_chat_session.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <fstream>
5#include <iomanip>
6#include <iostream>
7
8#ifdef _WIN32
9#include <conio.h>
10#include <io.h>
11#define isatty _isatty
12#define fileno _fileno
13#else
14#include <unistd.h>
15#endif
16
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"
22
23namespace yaze {
24namespace cli {
25namespace agent {
26
28
32
33namespace {
34
35std::string EscapeJsonString(absl::string_view input) {
36 std::string output;
37 output.reserve(input.size());
38 for (char ch : input) {
39 switch (ch) {
40 case '\\':
41 output.append("\\\\");
42 break;
43 case '"':
44 output.append("\\\"");
45 break;
46 case '\b':
47 output.append("\\b");
48 break;
49 case '\f':
50 output.append("\\f");
51 break;
52 case '\n':
53 output.append("\\n");
54 break;
55 case '\r':
56 output.append("\\r");
57 break;
58 case '\t':
59 output.append("\\t");
60 break;
61 default: {
62 unsigned char code = static_cast<unsigned char>(ch);
63 if (code < 0x20) {
64 absl::StrAppendFormat(&output, "\\u%04x",
65 static_cast<unsigned int>(code));
66 } else {
67 output.push_back(ch);
68 }
69 break;
70 }
71 }
72 }
73 return output;
74}
75
76std::string QuoteJson(absl::string_view value) {
77 return absl::StrCat("\"", EscapeJsonString(value), "\"");
78}
79
80std::string TableToJson(const ChatMessage::TableData& table) {
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));
85 }
86
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) {
93 cell_entries.push_back(QuoteJson(cell));
94 }
95 row_entries.push_back(
96 absl::StrCat("[", absl::StrJoin(cell_entries, ","), "]"));
97 }
98
99 return absl::StrCat("{\"headers\":[", absl::StrJoin(header_entries, ","),
100 "],\"rows\":[", absl::StrJoin(row_entries, ","), "]}");
101}
102
103std::string MetricsToJson(const ChatMessage::SessionMetrics& metrics) {
104 return absl::StrCat("{\"turn_index\":", metrics.turn_index,
105 ","
106 "\"total_user_messages\":",
107 metrics.total_user_messages,
108 ","
109 "\"total_agent_messages\":",
110 metrics.total_agent_messages,
111 ","
112 "\"total_tool_calls\":",
113 metrics.total_tool_calls,
114 ","
115 "\"total_commands\":",
116 metrics.total_commands,
117 ","
118 "\"total_proposals\":",
119 metrics.total_proposals,
120 ","
121 "\"total_elapsed_seconds\":",
122 metrics.total_elapsed_seconds,
123 ","
124 "\"average_latency_seconds\":",
125 metrics.average_latency_seconds, "}");
126}
127
128std::string MessageToJson(const ChatMessage& msg, bool show_timestamp) {
129 std::string json = "{";
130 absl::StrAppend(&json, "\"sender\":\"",
131 msg.sender == ChatMessage::Sender::kUser ? "user" : "agent",
132 "\"");
133
134 absl::StrAppend(&json, ",\"message\":", QuoteJson(msg.message));
135
136 if (msg.json_pretty.has_value()) {
137 absl::StrAppend(&json,
138 ",\"structured\":", QuoteJson(msg.json_pretty.value()));
139 }
140
141 if (msg.table_data.has_value()) {
142 absl::StrAppend(&json, ",\"table\":", TableToJson(*msg.table_data));
143 }
144
145 if (msg.metrics.has_value()) {
146 absl::StrAppend(&json, ",\"metrics\":", MetricsToJson(*msg.metrics));
147 }
148
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));
153 }
154
155 absl::StrAppend(&json, "}");
156 return json;
157}
158
160 if (table.headers.empty()) {
161 return;
162 }
163
164 std::cout << "\n|";
165 for (const auto& header : table.headers) {
166 std::cout << " " << header << " |";
167 }
168 std::cout << "\n|";
169 for (size_t i = 0; i < table.headers.size(); ++i) {
170 std::cout << " --- |";
171 }
172 std::cout << "\n";
173
174 for (const auto& row : table.rows) {
175 std::cout << "|";
176 for (size_t i = 0; i < table.headers.size(); ++i) {
177 if (i < row.size()) {
178 std::cout << " " << row[i];
179 }
180 std::cout << " |";
181 }
182 std::cout << "\n";
183 }
184}
185
187 std::cout << "\n> _Turn " << metrics.turn_index
188 << ": users=" << metrics.total_user_messages
189 << ", agents=" << metrics.total_agent_messages
190 << ", tool-calls=" << metrics.total_tool_calls
191 << ", commands=" << metrics.total_commands
192 << ", proposals=" << metrics.total_proposals << ", elapsed="
193 << absl::StrFormat("%.2fs avg %.2fs", metrics.total_elapsed_seconds,
195 << "_\n";
196}
197
199 return MetricsToJson(metrics);
200}
201
202} // namespace
203
205 if (table.headers.empty())
206 return;
207
208 // Calculate column widths
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();
212 }
213
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());
217 }
218 }
219
220 // Print header
221 std::cout << " ";
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];
224 }
225 std::cout << "\n ";
226 for (size_t i = 0; i < table.headers.size(); ++i) {
227 std::cout << std::string(col_widths[i] + 2, '-');
228 }
229 std::cout << "\n";
230
231 // Print rows
232 for (const auto& row : table.rows) {
233 std::cout << " ";
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];
236 }
237 std::cout << "\n";
238 }
239}
240
242 bool show_timestamp) {
243 switch (config_.output_format) {
245 const char* sender =
246 (msg.sender == ChatMessage::Sender::kUser) ? "You" : "Agent";
247
248 if (show_timestamp) {
249 std::string timestamp =
250 absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone());
251 std::cout << "[" << timestamp << "] ";
252 }
253
254 std::cout << sender << ": ";
255
256 if (msg.table_data.has_value()) {
257 std::cout << "\n";
258 PrintTable(msg.table_data.value());
259 } else if (msg.json_pretty.has_value()) {
260 std::cout << "\n" << msg.json_pretty.value() << "\n";
261 } else {
262 std::cout << msg.message << "\n";
263 }
264
265 if (msg.metrics.has_value()) {
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)
276 << "\n";
277 }
278 break;
279 }
281 if (msg.json_pretty.has_value()) {
282 std::cout << msg.json_pretty.value() << "\n";
283 } else if (msg.table_data.has_value()) {
284 PrintTable(msg.table_data.value());
285 } else {
286 std::cout << msg.message << "\n";
287 }
288 break;
289 }
291 std::cout << (msg.sender == ChatMessage::Sender::kUser ? "**You:** "
292 : "**Agent:** ");
293 if (msg.table_data.has_value()) {
294 PrintMarkdownTable(msg.table_data.value());
295 } else if (msg.json_pretty.has_value()) {
296 std::cout << "\n```json\n" << msg.json_pretty.value() << "\n```\n";
297 } else {
298 std::cout << msg.message << "\n";
299 }
300
301 if (msg.metrics.has_value()) {
302 PrintMarkdownMetrics(*msg.metrics);
303 }
304 break;
305 }
307 std::cout << MessageToJson(msg, show_timestamp) << std::endl;
308 break;
309 }
310 }
311}
312
314 const std::string& message, std::string* response_out) {
315 auto result = agent_service_.SendMessage(message);
316 if (!result.ok()) {
317 return result.status();
318 }
319
320 const auto& response_msg = result.value();
321
322 if (response_out != nullptr) {
323 *response_out = response_msg.message;
324 }
325
326 return absl::OkStatus();
327}
328
330 // Check if stdin is a TTY (interactive) or a pipe/file
331 bool is_interactive = isatty(fileno(stdin));
332
333 if (is_interactive && config_.output_format == AgentOutputFormat::kFriendly) {
334 std::cout << "Z3ED Agent Chat (Simple Mode)\n";
336 std::cout << "Vim mode enabled! Use hjkl to move, i for insert, ESC for "
337 "normal mode.\n";
338 }
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";
342 }
343
344 std::string input;
345 while (true) {
346 // Read input with or without vim mode
347 if (config_.enable_vim_mode && is_interactive) {
348 input = ReadLineWithVim();
349 if (input.empty()) {
350 // EOF reached
352 std::cout << "\n";
353 }
354 break;
355 }
356 } else {
357 if (is_interactive && config_.output_format != AgentOutputFormat::kJson) {
358 std::cout << "You: ";
359 std::cout.flush(); // Ensure prompt is displayed before reading
360 }
361
362 if (!std::getline(std::cin, input)) {
363 // EOF reached (piped input exhausted or Ctrl+D)
364 if (is_interactive &&
366 std::cout << "\n";
367 }
368 break;
369 }
370 }
371
372 if (input.empty())
373 continue;
374 if (input == "quit" || input == "exit")
375 break;
376
377 if (input == "reset") {
378 Reset();
380 std::cout << "{\"event\":\"history_cleared\"}" << std::endl;
382 std::cout << "> Conversation history cleared.\n\n";
383 } else {
384 std::cout << "Conversation history cleared.\n\n";
385 }
386 continue;
387 }
388
389 auto result = agent_service_.SendMessage(input);
390 if (!result.ok()) {
392 std::cout << absl::StrCat("{\"event\":\"error\",\"message\":",
393 QuoteJson(result.status().message()), "}")
394 << std::endl;
396 std::cout << "> **Error:** " << result.status().message() << "\n\n";
398 std::cout << "error: " << result.status().message() << "\n";
399 } else {
400 std::cerr << "Error: " << result.status().message() << "\n\n";
401 }
402 continue;
403 }
404
405 PrintMessage(result.value(), false);
407 std::cout << "\n";
408 }
409 }
410
411 const auto metrics = agent_service_.GetMetrics();
413 std::cout << absl::StrCat("{\"event\":\"session_summary\",\"metrics\":",
414 SessionMetricsToJson(metrics), "}")
415 << std::endl;
417 std::cout << "\n> **Session totals** ";
418 std::cout << "turns=" << metrics.turn_index
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)
427 << "\n\n";
428 } else {
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);
438 }
439
440 return absl::OkStatus();
441}
442
443absl::Status SimpleChatSession::RunBatch(const std::string& input_file) {
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));
448 }
449
451 std::cout << "Running batch session from: " << input_file << "\n";
452 std::cout << "----------------------------------------\n\n";
454 std::cout << "### Batch session: " << input_file << "\n\n";
455 }
456
457 std::string line;
458 int line_num = 0;
459 while (std::getline(file, line)) {
460 ++line_num;
461
462 // Skip empty lines and comments
463 if (line.empty() || line[0] == '#')
464 continue;
465
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), "}")
473 << std::endl;
474 }
475
476 auto result = agent_service_.SendMessage(line);
477 if (!result.ok()) {
479 std::cout << absl::StrCat("{\"event\":\"error\",\"index\":", line_num,
480 ",\"message\":",
481 QuoteJson(result.status().message()), "}")
482 << std::endl;
484 std::cout << " - ⚠️ " << result.status().message() << "\n";
486 std::cout << "error@" << line_num << ": " << result.status().message()
487 << "\n";
488 } else {
489 std::cerr << "Error: " << result.status().message() << "\n\n";
490 }
491 continue;
492 }
493
494 PrintMessage(result.value(), false);
496 std::cout << "\n";
497 }
498 }
499
500 const auto metrics = agent_service_.GetMetrics();
502 std::cout << absl::StrCat("{\"event\":\"session_summary\",\"metrics\":",
503 SessionMetricsToJson(metrics), "}")
504 << std::endl;
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)
515 << "\n\n";
516 } else {
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);
526 }
527
528 return absl::OkStatus();
529}
530
532 if (!vim_mode_) {
533 vim_mode_ = std::make_unique<VimMode>();
534 vim_mode_->SetAutoCompleteCallback([this](const std::string& partial) {
535 return GetAutocompleteOptions(partial);
536 });
537 }
538
539 vim_mode_->Reset();
540
541 // Show initial prompt
542 std::cout << "You [" << vim_mode_->GetModeString() << "]: " << std::flush;
543
544 while (true) {
545 int ch;
546#ifdef _WIN32
547 ch = _getch();
548#else
549 unsigned char c;
550 if (read(STDIN_FILENO, &c, 1) == 1) {
551 ch = static_cast<int>(c);
552 } else {
553 break; // EOF
554 }
555#endif
556
557 if (vim_mode_->ProcessKey(ch)) {
558 // Line complete
559 std::string line = vim_mode_->GetLine();
560 vim_mode_->AddToHistory(line);
561 std::cout << "\n";
562 return line;
563 }
564 }
565
566 return ""; // EOF
567}
568
570 const std::string& partial) {
571 // Simple autocomplete with common commands
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 "};
576
577 std::vector<std::string> matches;
578 for (const auto& cmd : all_commands) {
579 if (cmd.find(partial) == 0) {
580 matches.push_back(cmd);
581 }
582 }
583
584 return matches;
585}
586
587} // namespace agent
588} // namespace cli
589} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::StatusOr< ChatMessage > SendMessage(const std::string &message)
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_
std::unique_ptr< VimMode > vim_mode_
absl::Status SendAndWaitForResponse(const std::string &message, std::string *response_out=nullptr)
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)
std::string MessageToJson(const ChatMessage &msg, bool show_timestamp)
std::vector< std::vector< std::string > > rows
std::optional< std::string > json_pretty
std::optional< SessionMetrics > metrics