Step 155: add agent code generation

This commit is contained in:
Bill
2026-02-09 19:21:17 -07:00
parent cc186f628f
commit 7340d5028a
6 changed files with 191 additions and 1 deletions

View File

@@ -876,6 +876,10 @@ add_executable(step154_test tests/step154_test.cpp)
target_include_directories(step154_test PRIVATE src)
target_link_libraries(step154_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step155_test tests/step155_test.cpp)
target_include_directories(step155_test PRIVATE src)
target_link_libraries(step155_test PRIVATE nlohmann_json::nlohmann_json)
find_package(SDL2 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(glad CONFIG REQUIRED)

86
editor/src/AgentCodeGen.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include "PrimitivesRegistry.h"
#include "ast/Function.h"
#include "ast/Parameter.h"
#include "ast/Statement.h"
#include "ast/Expression.h"
#include <string>
#include <vector>
#include <algorithm>
struct AgentCodeGenResult {
ASTNode* node = nullptr;
std::string note;
std::vector<std::string> usedSymbols;
};
class AgentCodeGen {
public:
AgentCodeGenResult generate(const std::string& spec,
PrimitivesRegistry& registry,
const std::string& language,
bool preferImports) const {
AgentCodeGenResult result;
auto functions = registry.getAvailableFunctions();
if (functions.empty()) {
result.note = "No available functions; returning empty stub.";
result.node = makeEmptyFunction("generated");
return result;
}
PrimitiveSymbol chosen = chooseFunction(functions, preferImports);
result.usedSymbols.push_back(chosen.name);
auto* fn = new Function(nextId("fn"), "generated");
auto* param = new Parameter(nextId("param"), "input");
fn->addChild("parameters", param);
auto* call = new FunctionCall();
call->id = nextId("call");
call->functionName = chosen.name;
call->addChild("arguments", new VariableReference(nextId("var"), "input"));
auto* stmt = new ExpressionStatement();
stmt->id = nextId("exprstmt");
stmt->setChild("expression", call);
fn->addChild("body", stmt);
result.node = fn;
result.note = buildNote(spec, chosen, language);
return result;
}
private:
static PrimitiveSymbol chooseFunction(const std::vector<PrimitiveSymbol>& funcs,
bool preferImports) {
if (preferImports) {
for (const auto& sym : funcs) {
if (sym.source == "import") return sym;
}
}
for (const auto& sym : funcs) {
if (sym.source == "builtin") return sym;
}
return funcs.front();
}
static Function* makeEmptyFunction(const std::string& name) {
auto* fn = new Function(nextId("fn"), name);
return fn;
}
static std::string buildNote(const std::string& spec,
const PrimitiveSymbol& sym,
const std::string& language) {
std::string note = "Generated using " + sym.name + " (" + sym.source + ")";
if (!spec.empty()) note += " for spec: " + spec;
if (!language.empty()) note += " in " + language;
return note;
}
static std::string nextId(const std::string& prefix) {
static int counter = 0;
return prefix + "_" + std::to_string(counter++);
}
};

View File

@@ -49,6 +49,7 @@
#include "ImportManager.h"
#include "PrimitivesRegistry.h"
#include "AgentLibraryPolicy.h"
#include "AgentCodeGen.h"
#include "CompositionPanel.h"
#include "IncrementalOptimizer.h"
#include "EmacsIntegration.h"
@@ -1124,6 +1125,42 @@ struct EditorState {
return response;
}
if (method == "generateCode") {
if (!active() || !isStructured()) {
response["error"] = {{"code", -32000}, {"message", "No structured buffer"}};
return response;
}
Module* ast = activeAST();
if (!ast) {
response["error"] = {{"code", -32001}, {"message", "AST unavailable"}};
return response;
}
auto params = request.contains("params") ? request["params"] : json::object();
std::string spec = params.value("spec", "");
bool preferImports = params.value("preferImports", true);
primitives.setRoot(ast);
primitives.setLanguage(active()->language);
AgentCodeGen gen;
AgentCodeGenResult genRes = gen.generate(spec, primitives, active()->language, preferImports);
if (!genRes.node) {
response["error"] = {{"code", -32020}, {"message", "Code generation failed"}};
return response;
}
json nodeJson = toJson(genRes.node);
deleteTree(genRes.node);
response["result"] = {
{"node", nodeJson},
{"note", genRes.note},
{"usedSymbols", genRes.usedSymbols},
{"language", active()->language}
};
return response;
}
if (method == "applyMutation") {
auto permIt = agentMutationPermissions.find(sessionId);
if (permIt == agentMutationPermissions.end() || !permIt->second) {

View File

@@ -0,0 +1,62 @@
// Step 155 TDD Test: Agent library-aware code generation
#include "AgentCodeGen.h"
#include "ast/Module.h"
#include "ast/ExternalModule.h"
#include "ast/TypeSignature.h"
#include "ast/Serialization.h"
#include <iostream>
static void expect(bool cond, const std::string& name, int& passed, int& failed) {
if (cond) {
std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n";
++passed;
} else {
std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n";
++failed;
}
}
static bool containsFunctionCall(ASTNode* node, const std::string& name) {
if (!node) return false;
if (node->conceptType == "FunctionCall") {
auto* call = static_cast<FunctionCall*>(node);
return call->functionName == name;
}
for (auto* child : node->allChildren()) {
if (containsFunctionCall(child, name)) return true;
}
return false;
}
int main() {
int passed = 0;
int failed = 0;
Module mod("m1", "Module", "python");
auto* ext = new ExternalModule("ext_numpy", "numpy", "python");
ext->addChild("signatures", new TypeSignature("sig_array", "numpy.array"));
mod.addChild("externalModules", ext);
PrimitivesRegistry reg;
reg.setRoot(&mod);
reg.setLanguage("python");
AgentCodeGen gen;
auto res = gen.generate("create array", reg, "python", true);
expect(res.node != nullptr, "agent code generated", passed, failed);
expect(containsFunctionCall(res.node, "numpy.array"),
"imports preferred for generation", passed, failed);
deleteTree(res.node);
Module mod2("m2", "Module", "python");
PrimitivesRegistry reg2;
reg2.setRoot(&mod2);
reg2.setLanguage("python");
auto res2 = gen.generate("print", reg2, "python", true);
expect(res2.node != nullptr, "fallback generation", passed, failed);
deleteTree(res2.node);
std::cout << "\n=== Step 155 Results: " << passed << " passed, "
<< failed << " failed ===\n";
return failed == 0 ? 0 : 1;
}