From f770f173a04a7dc33bf86a194284de0e41b355c7 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 21:10:53 -0700 Subject: [PATCH] Add step 625 tool call visualization model --- editor/CMakeLists.txt | 9 ++ editor/src/AgentChatPanelModel.h | 17 +++ editor/src/AgentToolCallVisualization.h | 63 ++++++++++ editor/src/panels/AgentChatPanel.h | 22 ++++ editor/tests/step625_test.cpp | 155 ++++++++++++++++++++++++ progress.md | 35 ++++++ 6 files changed, 301 insertions(+) create mode 100644 editor/src/AgentToolCallVisualization.h create mode 100644 editor/tests/step625_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 04ce7c8..b615546 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -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) diff --git a/editor/src/AgentChatPanelModel.h b/editor/src/AgentChatPanelModel.h index 7a59bb7..87ba09c 100644 --- a/editor/src/AgentChatPanelModel.h +++ b/editor/src/AgentChatPanelModel.h @@ -1,6 +1,8 @@ #pragma once // Step 624: Agent Chat Panel model +#include "AgentToolCallVisualization.h" + #include #include #include @@ -19,6 +21,7 @@ struct AgentChatMessage { struct AgentChatState { std::vector messages; + std::vector 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"; diff --git a/editor/src/AgentToolCallVisualization.h b/editor/src/AgentToolCallVisualization.h new file mode 100644 index 0000000..539ba52 --- /dev/null +++ b/editor/src/AgentToolCallVisualization.h @@ -0,0 +1,63 @@ +#pragma once +// Step 625: Tool call visualization model + +#include + +#include + +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"; + } +}; diff --git a/editor/src/panels/AgentChatPanel.h b/editor/src/panels/AgentChatPanel.h index f8e1b31..9ba1eb9 100644 --- a/editor/src/panels/AgentChatPanel.h +++ b/editor/src/panels/AgentChatPanel.h @@ -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(); diff --git a/editor/tests/step625_test.cpp b/editor/tests/step625_test.cpp new file mode 100644 index 0000000..f21e849 --- /dev/null +++ b/editor/tests/step625_test.cpp @@ -0,0 +1,155 @@ +// Step 625: Tool call visualization (12 tests) + +#include "AgentChatPanelModel.h" +#include "AgentToolCallVisualization.h" + +#include + +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; +} diff --git a/progress.md b/progress.md index 38fd9d8..cfedb81 100644 --- a/progress.md +++ b/progress.md @@ -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`