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 <io.h>
10#include <conio.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}
103
104std::string MetricsToJson(const ChatMessage::SessionMetrics& metrics) {
105 return absl::StrCat(
106 "{\"turn_index\":", metrics.turn_index, ","
107 "\"total_user_messages\":", metrics.total_user_messages, ","
108 "\"total_agent_messages\":", metrics.total_agent_messages, ","
109 "\"total_tool_calls\":", metrics.total_tool_calls, ","
110 "\"total_commands\":", metrics.total_commands, ","
111 "\"total_proposals\":", metrics.total_proposals, ","
112 "\"total_elapsed_seconds\":", metrics.total_elapsed_seconds, ","
113 "\"average_latency_seconds\":", metrics.average_latency_seconds, "}");
114}
115
116std::string MessageToJson(const ChatMessage& msg, bool show_timestamp) {
117 std::string json = "{";
118 absl::StrAppend(&json, "\"sender\":\"",
119 msg.sender == ChatMessage::Sender::kUser ? "user"
120 : "agent",
121 "\"");
122
123 absl::StrAppend(&json, ",\"message\":", QuoteJson(msg.message));
124
125 if (msg.json_pretty.has_value()) {
126 absl::StrAppend(&json, ",\"structured\":",
127 QuoteJson(msg.json_pretty.value()));
128 }
129
130 if (msg.table_data.has_value()) {
131 absl::StrAppend(&json, ",\"table\":", TableToJson(*msg.table_data));
132 }
133
134 if (msg.metrics.has_value()) {
135 absl::StrAppend(&json, ",\"metrics\":",
136 MetricsToJson(*msg.metrics));
137 }
138
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));
144 }
145
146 absl::StrAppend(&json, "}");
147 return json;
148}
149
151 if (table.headers.empty()) {
152 return;
153 }
154
155 std::cout << "\n|";
156 for (const auto& header : table.headers) {
157 std::cout << " " << header << " |";
158 }
159 std::cout << "\n|";
160 for (size_t i = 0; i < table.headers.size(); ++i) {
161 std::cout << " --- |";
162 }
163 std::cout << "\n";
164
165 for (const auto& row : table.rows) {
166 std::cout << "|";
167 for (size_t i = 0; i < table.headers.size(); ++i) {
168 if (i < row.size()) {
169 std::cout << " " << row[i];
170 }
171 std::cout << " |";
172 }
173 std::cout << "\n";
174 }
175}
176
178 std::cout << "\n> _Turn " << metrics.turn_index
179 << ": users=" << metrics.total_user_messages
180 << ", agents=" << metrics.total_agent_messages
181 << ", tool-calls=" << metrics.total_tool_calls
182 << ", commands=" << metrics.total_commands
183 << ", proposals=" << metrics.total_proposals
184 << ", elapsed="
185 << absl::StrFormat("%.2fs avg %.2fs",
186 metrics.total_elapsed_seconds,
188 << "_\n";
189}
190
192 return MetricsToJson(metrics);
193}
194
195} // namespace
196
198 if (table.headers.empty()) return;
199
200 // Calculate column widths
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();
204 }
205
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());
209 }
210 }
211
212 // Print header
213 std::cout << " ";
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];
216 }
217 std::cout << "\n ";
218 for (size_t i = 0; i < table.headers.size(); ++i) {
219 std::cout << std::string(col_widths[i] + 2, '-');
220 }
221 std::cout << "\n";
222
223 // Print rows
224 for (const auto& row : table.rows) {
225 std::cout << " ";
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];
228 }
229 std::cout << "\n";
230 }
231}
232
234 bool show_timestamp) {
235 switch (config_.output_format) {
237 const char* sender =
238 (msg.sender == ChatMessage::Sender::kUser) ? "You" : "Agent";
239
240 if (show_timestamp) {
241 std::string timestamp = absl::FormatTime(
242 "%H:%M:%S", msg.timestamp, absl::LocalTimeZone());
243 std::cout << "[" << timestamp << "] ";
244 }
245
246 std::cout << sender << ": ";
247
248 if (msg.table_data.has_value()) {
249 std::cout << "\n";
250 PrintTable(msg.table_data.value());
251 } else if (msg.json_pretty.has_value()) {
252 std::cout << "\n" << msg.json_pretty.value() << "\n";
253 } else {
254 std::cout << msg.message << "\n";
255 }
256
257 if (msg.metrics.has_value()) {
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
265 << ", elapsed: "
266 << absl::StrFormat("%.2fs avg %.2fs",
267 metrics.total_elapsed_seconds,
268 metrics.average_latency_seconds)
269 << "\n";
270 }
271 break;
272 }
274 if (msg.json_pretty.has_value()) {
275 std::cout << msg.json_pretty.value() << "\n";
276 } else if (msg.table_data.has_value()) {
277 PrintTable(msg.table_data.value());
278 } else {
279 std::cout << msg.message << "\n";
280 }
281 break;
282 }
284 std::cout << (msg.sender == ChatMessage::Sender::kUser ? "**You:** "
285 : "**Agent:** ");
286 if (msg.table_data.has_value()) {
287 PrintMarkdownTable(msg.table_data.value());
288 } else if (msg.json_pretty.has_value()) {
289 std::cout << "\n```json\n" << msg.json_pretty.value()
290 << "\n```\n";
291 } else {
292 std::cout << msg.message << "\n";
293 }
294
295 if (msg.metrics.has_value()) {
296 PrintMarkdownMetrics(*msg.metrics);
297 }
298 break;
299 }
301 std::cout << MessageToJson(msg, show_timestamp) << std::endl;
302 break;
303 }
304 }
305}
306
308 const std::string& message, std::string* response_out) {
309 auto result = agent_service_.SendMessage(message);
310 if (!result.ok()) {
311 return result.status();
312 }
313
314 const auto& response_msg = result.value();
315
316 if (response_out != nullptr) {
317 *response_out = response_msg.message;
318 }
319
320 return absl::OkStatus();
321}
322
324 // Check if stdin is a TTY (interactive) or a pipe/file
325 bool is_interactive = isatty(fileno(stdin));
326
327 if (is_interactive && config_.output_format == AgentOutputFormat::kFriendly) {
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";
331 }
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";
335 }
336
337 std::string input;
338 while (true) {
339 // Read input with or without vim mode
340 if (config_.enable_vim_mode && is_interactive) {
341 input = ReadLineWithVim();
342 if (input.empty()) {
343 // EOF reached
345 std::cout << "\n";
346 }
347 break;
348 }
349 } else {
350 if (is_interactive && config_.output_format != AgentOutputFormat::kJson) {
351 std::cout << "You: ";
352 std::cout.flush(); // Ensure prompt is displayed before reading
353 }
354
355 if (!std::getline(std::cin, input)) {
356 // EOF reached (piped input exhausted or Ctrl+D)
357 if (is_interactive && config_.output_format != AgentOutputFormat::kJson) {
358 std::cout << "\n";
359 }
360 break;
361 }
362 }
363
364 if (input.empty()) continue;
365 if (input == "quit" || input == "exit") break;
366
367 if (input == "reset") {
368 Reset();
370 std::cout << "{\"event\":\"history_cleared\"}" << std::endl;
372 std::cout << "> Conversation history cleared.\n\n";
373 } else {
374 std::cout << "Conversation history cleared.\n\n";
375 }
376 continue;
377 }
378
379 auto result = agent_service_.SendMessage(input);
380 if (!result.ok()) {
382 std::cout << absl::StrCat(
383 "{\"event\":\"error\",\"message\":",
384 QuoteJson(result.status().message()), "}")
385 << std::endl;
387 std::cout << "> **Error:** " << result.status().message() << "\n\n";
389 std::cout << "error: " << result.status().message() << "\n";
390 } else {
391 std::cerr << "Error: " << result.status().message() << "\n\n";
392 }
393 continue;
394 }
395
396 PrintMessage(result.value(), false);
398 std::cout << "\n";
399 }
400 }
401
402 const auto metrics = agent_service_.GetMetrics();
404 std::cout << absl::StrCat("{\"event\":\"session_summary\",\"metrics\":",
405 SessionMetricsToJson(metrics), "}")
406 << std::endl;
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)
418 << "\n\n";
419 } else {
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
426 << ", elapsed: "
427 << absl::StrFormat("%.2fs avg %.2fs\n\n",
428 metrics.total_elapsed_seconds,
429 metrics.average_latency_seconds);
430 }
431
432 return absl::OkStatus();
433}
434
435absl::Status SimpleChatSession::RunBatch(const std::string& input_file) {
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));
440 }
441
443 std::cout << "Running batch session from: " << input_file << "\n";
444 std::cout << "----------------------------------------\n\n";
446 std::cout << "### Batch session: " << input_file << "\n\n";
447 }
448
449 std::string line;
450 int line_num = 0;
451 while (std::getline(file, line)) {
452 ++line_num;
453
454 // Skip empty lines and comments
455 if (line.empty() || line[0] == '#') continue;
456
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), "}")
465 << std::endl;
466 }
467
468 auto result = agent_service_.SendMessage(line);
469 if (!result.ok()) {
471 std::cout << absl::StrCat(
472 "{\"event\":\"error\",\"index\":", line_num,
473 ",\"message\":",
474 QuoteJson(result.status().message()), "}")
475 << std::endl;
477 std::cout << " - ⚠️ " << result.status().message() << "\n";
479 std::cout << "error@" << line_num << ": "
480 << result.status().message() << "\n";
481 } else {
482 std::cerr << "Error: " << result.status().message() << "\n\n";
483 }
484 continue;
485 }
486
487 PrintMessage(result.value(), false);
489 std::cout << "\n";
490 }
491 }
492
493 const auto metrics = agent_service_.GetMetrics();
495 std::cout << absl::StrCat("{\"event\":\"session_summary\",\"metrics\":",
496 SessionMetricsToJson(metrics), "}")
497 << std::endl;
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)
508 << "\n\n";
509 } else {
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
516 << ", elapsed: "
517 << absl::StrFormat("%.2fs avg %.2fs\n\n",
518 metrics.total_elapsed_seconds,
519 metrics.average_latency_seconds);
520 }
521
522 return absl::OkStatus();
523}
524
526 if (!vim_mode_) {
527 vim_mode_ = std::make_unique<VimMode>();
528 vim_mode_->SetAutoCompleteCallback(
529 [this](const std::string& partial) {
530 return GetAutocompleteOptions(partial);
531 });
532 }
533
534 vim_mode_->Reset();
535
536 // Show initial prompt
537 std::cout << "You [" << vim_mode_->GetModeString() << "]: " << std::flush;
538
539 while (true) {
540 int ch;
541#ifdef _WIN32
542 ch = _getch();
543#else
544 unsigned char c;
545 if (read(STDIN_FILENO, &c, 1) == 1) {
546 ch = static_cast<int>(c);
547 } else {
548 break; // EOF
549 }
550#endif
551
552 if (vim_mode_->ProcessKey(ch)) {
553 // Line complete
554 std::string line = vim_mode_->GetLine();
555 vim_mode_->AddToHistory(line);
556 std::cout << "\n";
557 return line;
558 }
559 }
560
561 return ""; // EOF
562}
563
565 const std::string& partial) {
566 // Simple autocomplete with common commands
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 "
571 };
572
573 std::vector<std::string> matches;
574 for (const auto& cmd : all_commands) {
575 if (cmd.find(partial) == 0) {
576 matches.push_back(cmd);
577 }
578 }
579
580 return matches;
581}
582
583} // namespace agent
584} // namespace cli
585} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
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)
Main namespace for the application.
std::vector< std::vector< std::string > > rows
std::optional< std::string > json_pretty
std::optional< SessionMetrics > metrics