Add step 625 tool call visualization model

This commit is contained in:
Bill
2026-02-17 21:10:53 -07:00
parent 49b80096f1
commit f770f173a0
6 changed files with 301 additions and 0 deletions

View File

@@ -4504,4 +4504,13 @@ target_link_libraries(step624_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step625_test tests/step625_test.cpp)
target_include_directories(step625_test PRIVATE src)
target_link_libraries(step625_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -1,6 +1,8 @@
#pragma once
// Step 624: Agent Chat Panel model
#include "AgentToolCallVisualization.h"
#include <cctype>
#include <string>
#include <vector>
@@ -19,6 +21,7 @@ struct AgentChatMessage {
struct AgentChatState {
std::vector<AgentChatMessage> messages;
std::vector<AgentToolCallView> toolCalls;
std::string draftInput;
bool autoScroll = true;
bool open = false;
@@ -52,6 +55,20 @@ public:
state->autoScroll = true;
}
static void addToolCall(AgentChatState* state,
const std::string& toolName,
const json& input,
const json& output,
const std::string& timestamp) {
if (!state) return;
AgentToolCallView view = AgentToolCallVisualization::build(toolName, input, output);
state->toolCalls.push_back(view);
state->messages.push_back({AgentChatRole::Tool,
AgentToolCallVisualization::inlineLabel(view),
timestamp});
state->autoScroll = true;
}
static const char* roleLabel(AgentChatRole role) {
if (role == AgentChatRole::User) return "user";
if (role == AgentChatRole::Assistant) return "assistant";

View File

@@ -0,0 +1,63 @@
#pragma once
// Step 625: Tool call visualization model
#include <nlohmann/json.hpp>
#include <string>
using json = nlohmann::json;
struct AgentToolCallView {
std::string toolName;
std::string summary;
std::string inputJson;
std::string outputJson;
bool expanded = false;
};
class AgentToolCallVisualization {
public:
static AgentToolCallView build(const std::string& toolName,
const json& input,
const json& output) {
AgentToolCallView view;
view.toolName = toolName;
view.summary = summarize(toolName, input);
view.inputJson = input.dump(2);
view.outputJson = output.dump(2);
return view;
}
static std::string inlineLabel(const AgentToolCallView& view) {
const std::string chevron = view.expanded ? "v" : ">";
return "[TOOL: " + view.toolName + "] " + chevron + " " + view.summary;
}
static void toggleExpanded(AgentToolCallView* view) {
if (!view) return;
view->expanded = !view->expanded;
}
private:
static std::string summarize(const std::string& toolName, const json& input) {
if (toolName == "whetstone_mutate") {
std::string type = input.value("type", "mutation");
std::string nodeId = input.value("nodeId", "?");
std::string property = input.value("property", "");
std::string value = input.contains("value") ? input["value"].dump() : "null";
if (!property.empty()) return type + " " + nodeId + "." + property + " -> " + value;
return type + " " + nodeId + " -> " + value;
}
if (toolName == "whetstone_generate_code") {
std::string spec = input.value("spec", "");
if (spec.size() > 40) spec = spec.substr(0, 40) + "...";
return "generate from spec: " + spec;
}
if (toolName == "whetstone_run_pipeline") {
std::string src = input.value("sourceLanguage", "?");
std::string tgt = input.value("targetLanguage", "?");
return "pipeline " + src + " -> " + tgt;
}
return "call";
}
};

View File

@@ -25,6 +25,28 @@ static void renderAgentChatPanel(EditorState& state) {
chat.autoScroll = false;
ImGui::EndChild();
if (!chat.toolCalls.empty()) {
ImGui::Separator();
ImGui::TextUnformatted("Tool Calls");
for (std::size_t i = 0; i < chat.toolCalls.size(); ++i) {
auto& call = chat.toolCalls[i];
std::string label = AgentToolCallVisualization::inlineLabel(call);
if (ImGui::CollapsingHeader((label + "##tool_" + std::to_string(i)).c_str())) {
call.expanded = true;
ImGui::TextUnformatted("Input");
ImGui::BeginChild(("##tool_in_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
ImGui::TextUnformatted(call.inputJson.c_str());
ImGui::EndChild();
ImGui::TextUnformatted("Output");
ImGui::BeginChild(("##tool_out_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
ImGui::TextUnformatted(call.outputJson.c_str());
ImGui::EndChild();
} else {
call.expanded = false;
}
}
}
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput;
InputTextMultilineStr("##AgentChatInput", &chat.draftInput, ImVec2(-90, 80), flags);
ImGui::SameLine();

View File

@@ -0,0 +1,155 @@
// Step 625: Tool call visualization (12 tests)
#include "AgentChatPanelModel.h"
#include "AgentToolCallVisualization.h"
#include <iostream>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
void test_build_sets_tool_name() {
TEST(build_sets_tool_name);
auto view = AgentToolCallVisualization::build("whetstone_mutate", json::object(), json::object());
CHECK(view.toolName == "whetstone_mutate", "tool name mismatch");
PASS();
}
void test_build_serializes_input_json() {
TEST(build_serializes_input_json);
auto view = AgentToolCallVisualization::build("whetstone_mutate",
{{"type", "setProperty"}},
json::object());
CHECK(view.inputJson.find("setProperty") != std::string::npos, "input json missing expected token");
PASS();
}
void test_build_serializes_output_json() {
TEST(build_serializes_output_json);
auto view = AgentToolCallVisualization::build("whetstone_mutate",
json::object(),
{{"ok", true}});
CHECK(view.outputJson.find("\"ok\": true") != std::string::npos, "output json missing expected token");
PASS();
}
void test_mutate_summary_includes_property_path() {
TEST(mutate_summary_includes_property_path);
auto view = AgentToolCallVisualization::build("whetstone_mutate",
{{"type", "setProperty"},
{"nodeId", "fn1"},
{"property", "name"},
{"value", "newName"}},
json::object());
CHECK(view.summary.find("fn1.name") != std::string::npos, "summary missing path");
CHECK(view.summary.find("newName") != std::string::npos, "summary missing value");
PASS();
}
void test_generate_code_summary_truncates_long_spec() {
TEST(generate_code_summary_truncates_long_spec);
std::string longSpec(80, 'x');
auto view = AgentToolCallVisualization::build("whetstone_generate_code",
{{"spec", longSpec}},
json::object());
CHECK(view.summary.find("...") != std::string::npos, "summary should be truncated");
PASS();
}
void test_pipeline_summary_includes_languages() {
TEST(pipeline_summary_includes_languages);
auto view = AgentToolCallVisualization::build("whetstone_run_pipeline",
{{"sourceLanguage", "python"},
{"targetLanguage", "cpp"}},
json::object());
CHECK(view.summary.find("python -> cpp") != std::string::npos, "language summary mismatch");
PASS();
}
void test_unknown_tool_uses_call_summary() {
TEST(unknown_tool_uses_call_summary);
auto view = AgentToolCallVisualization::build("whetstone_unknown",
json::object(),
json::object());
CHECK(view.summary == "call", "unexpected summary");
PASS();
}
void test_inline_label_uses_collapsed_chevron_by_default() {
TEST(inline_label_uses_collapsed_chevron_by_default);
auto view = AgentToolCallVisualization::build("whetstone_mutate", json::object(), json::object());
std::string label = AgentToolCallVisualization::inlineLabel(view);
CHECK(label.find(">") != std::string::npos, "collapsed chevron missing");
PASS();
}
void test_toggle_expanded_updates_label_chevron() {
TEST(toggle_expanded_updates_label_chevron);
auto view = AgentToolCallVisualization::build("whetstone_mutate", json::object(), json::object());
AgentToolCallVisualization::toggleExpanded(&view);
std::string label = AgentToolCallVisualization::inlineLabel(view);
CHECK(label.find("v") != std::string::npos, "expanded chevron missing");
PASS();
}
void test_chat_add_tool_call_appends_message() {
TEST(chat_add_tool_call_appends_message);
AgentChatState state;
AgentChatPanelModel::addToolCall(&state,
"whetstone_mutate",
{{"type", "setProperty"}, {"nodeId", "fn1"}},
{{"ok", true}},
"11:00");
CHECK(state.messages.size() == 1, "message should be added");
CHECK(state.messages[0].role == AgentChatRole::Tool, "message role should be tool");
PASS();
}
void test_chat_add_tool_call_appends_tool_record() {
TEST(chat_add_tool_call_appends_tool_record);
AgentChatState state;
AgentChatPanelModel::addToolCall(&state,
"whetstone_run_pipeline",
{{"sourceLanguage", "python"}, {"targetLanguage", "rust"}},
{{"generatedCode", "fn main() {}"}},
"11:01");
CHECK(state.toolCalls.size() == 1, "tool call record should be added");
CHECK(state.toolCalls[0].toolName == "whetstone_run_pipeline", "tool call name mismatch");
PASS();
}
void test_chat_tool_message_contains_inline_tool_label() {
TEST(chat_tool_message_contains_inline_tool_label);
AgentChatState state;
AgentChatPanelModel::addToolCall(&state,
"whetstone_mutate",
{{"type", "setProperty"}, {"nodeId", "fn1"}, {"property", "name"}, {"value", "x"}},
{{"ok", true}},
"11:02");
CHECK(state.messages[0].content.find("[TOOL: whetstone_mutate]") != std::string::npos,
"tool inline label missing");
PASS();
}
int main() {
std::cout << "Step 625: Tool call visualization\n";
test_build_sets_tool_name(); // 1
test_build_serializes_input_json(); // 2
test_build_serializes_output_json(); // 3
test_mutate_summary_includes_property_path(); // 4
test_generate_code_summary_truncates_long_spec(); // 5
test_pipeline_summary_includes_languages(); // 6
test_unknown_tool_uses_call_summary(); // 7
test_inline_label_uses_collapsed_chevron_by_default(); // 8
test_toggle_expanded_updates_label_chevron(); // 9
test_chat_add_tool_call_appends_message(); // 10
test_chat_add_tool_call_appends_tool_record(); // 11
test_chat_tool_message_contains_inline_tool_label(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -12458,3 +12458,38 @@ role/timestamp metadata, and a dedicated chat panel surface.
- `editor/src/state/UIFlags.h` (`35` <= `600`)
- `editor/src/main.cpp` (`590` <= `1500`)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 625: Tool call visualization
**Status:** PASS (12/12 tests)
Adds structured tool-call visualization for agent activity, including inline
labels, tool-specific summaries, collapsible call records, and input/output
JSON payload capture.
**Files added:**
- `editor/src/AgentToolCallVisualization.h` - tool call view model:
- summary generation for mutate/generate/pipeline calls
- inline label formatting and expand/collapse state helper
- `editor/tests/step625_test.cpp` - 12 tests covering:
- summary/label generation behavior
- JSON payload capture
- expand/collapse toggling
- chat model integration with stored tool call entries
**Files modified:**
- `editor/src/AgentChatPanelModel.h` - added tool call list state + `addToolCall()` helper
- `editor/src/panels/AgentChatPanel.h` - renders tool calls as collapsible blocks with input/output JSON
- `editor/CMakeLists.txt` - `step625_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step625_test step624_test` - PASS
- `./editor/build-native/step625_test` - PASS (12/12)
- `./editor/build-native/step624_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/AgentToolCallVisualization.h` (`63` <= `600`)
- `editor/src/AgentChatPanelModel.h` (`91` <= `600`)
- `editor/src/panels/AgentChatPanel.h` (`58` <= `600`)
- `editor/tests/step625_test.cpp` within test-file size guidance (`155` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`