Add step 625 tool call visualization model
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
63
editor/src/AgentToolCallVisualization.h
Normal file
63
editor/src/AgentToolCallVisualization.h
Normal 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";
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
155
editor/tests/step625_test.cpp
Normal file
155
editor/tests/step625_test.cpp
Normal 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;
|
||||
}
|
||||
35
progress.md
35
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`
|
||||
|
||||
Reference in New Issue
Block a user