Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions tools/agent/server/agent-routes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>

Expand Down Expand Up @@ -75,6 +76,22 @@ struct sse_stream_res : server_http_res {
}
};

// Wrapper that holds shared_ptr to sse_stream_res to extend its lifetime
// This ensures the SSE response object lives until both:
// 1. The HTTP framework is done with it
// 2. The worker thread callback is done with it
struct sse_shared_wrapper : server_http_res {
std::shared_ptr<sse_stream_res> sse;

explicit sse_shared_wrapper(std::shared_ptr<sse_stream_res> s) : sse(std::move(s)) {
content_type = sse->content_type;
headers = sse->headers;
next = [this](std::string & output) -> bool {
return sse->next(output);
};
}
};

agent_routes::agent_routes(agent_session_manager & session_mgr)
: session_mgr_(session_mgr) {

Expand Down Expand Up @@ -106,6 +123,19 @@ agent_routes::agent_routes(agent_session_manager & session_mgr)
if (body.contains("working_dir")) {
config.working_dir = body["working_dir"].get<std::string>();
}
// Skills configuration
if (body.contains("enable_skills")) {
config.enable_skills = body["enable_skills"].get<bool>();
}
if (body.contains("skills_paths") && body["skills_paths"].is_array()) {
for (const auto & path : body["skills_paths"]) {
config.extra_skills_paths.push_back(path.get<std::string>());
}
}
// AGENTS.md configuration
if (body.contains("enable_agents_md")) {
config.enable_agents_md = body["enable_agents_md"].get<bool>();
}
} catch (const json::exception & e) {
return make_error(400, std::string("Invalid JSON: ") + e.what());
}
Expand Down Expand Up @@ -192,12 +222,14 @@ agent_routes::agent_routes(agent_session_manager & session_mgr)
return make_error(400, std::string("Invalid JSON: ") + e.what());
}

// Create SSE streaming response
auto sse_res = std::make_unique<sse_stream_res>();
auto * sse_ptr = sse_res.get();
// Create SSE streaming response with shared ownership
// The shared_ptr ensures the response lives until both:
// 1. The HTTP framework is done streaming
// 2. The worker thread callback is done
auto sse_shared = std::make_shared<sse_stream_res>();

// Start processing in background
session->send_message(content, [sse_ptr](const agent_event & event) {
// Start processing in background - capture shared_ptr to extend lifetime
session->send_message(content, [sse_shared](const agent_event & event) {
std::string event_type;
switch (event.type) {
case agent_event_type::TEXT_DELTA:
Expand All @@ -223,19 +255,20 @@ agent_routes::agent_routes(agent_session_manager & session_mgr)
break;
case agent_event_type::COMPLETED:
event_type = "completed";
sse_ptr->send(event_type, event.data);
sse_ptr->finish();
sse_shared->send(event_type, event.data);
sse_shared->finish();
return;
case agent_event_type::ERROR:
event_type = "error";
sse_ptr->send(event_type, event.data);
sse_ptr->finish();
sse_shared->send(event_type, event.data);
sse_shared->finish();
return;
}
sse_ptr->send(event_type, event.data);
sse_shared->send(event_type, event.data);
});

return sse_res;
// Return wrapper that holds shared_ptr reference
return std::make_unique<sse_shared_wrapper>(sse_shared);
};

// GET /v1/agent/session/:id/messages - Get conversation history
Expand Down
31 changes: 31 additions & 0 deletions tools/agent/server/agent-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
#include "llama.h"
#include "log.h"

// MCP support (Unix only)
#ifndef _WIN32
#include "../mcp/mcp-server-manager.h"
#include "../mcp/mcp-tool-wrapper.h"
#endif

#include <atomic>
#include <csignal>
#include <functional>
Expand Down Expand Up @@ -147,6 +153,28 @@ int main(int argc, char ** argv) {
ctx_http.is_ready.store(true);
LOG_INF("Model loaded successfully\n");

// Initialize MCP servers (Unix only)
// MCP manager must be declared here to outlive session manager (tools hold pointer to it)
#ifndef _WIN32
mcp_server_manager mcp_mgr;
int mcp_tools_count = 0;
std::string working_dir = "."; // Default working directory for MCP config search
std::string mcp_config = find_mcp_config(working_dir);
if (!mcp_config.empty()) {
LOG_INF("Loading MCP config from: %s\n", mcp_config.c_str());
if (mcp_mgr.load_config(mcp_config)) {
int started = mcp_mgr.start_servers();
if (started > 0) {
register_mcp_tools(mcp_mgr);
mcp_tools_count = static_cast<int>(mcp_mgr.list_all_tools().size());
LOG_INF("MCP: %d servers started, %d tools registered\n", started, mcp_tools_count);
}
}
}
#else
int mcp_tools_count = 0;
#endif

// Setup signal handlers
shutdown_handler = [&ctx_server](int) {
ctx_server.terminate();
Expand All @@ -171,6 +199,9 @@ int main(int argc, char ** argv) {
LOG_INF("llama-agent-server is listening on %s\n", ctx_http.listening_address.c_str());
LOG_INF("============================================\n");
LOG_INF("\n");
if (mcp_tools_count > 0) {
LOG_INF("MCP tools: %d\n", mcp_tools_count);
}
LOG_INF("API Endpoints:\n");
LOG_INF(" POST /v1/agent/session - Create a new session\n");
LOG_INF(" GET /v1/agent/session/:id - Get session info\n");
Expand Down
63 changes: 63 additions & 0 deletions tools/agent/server/agent-session.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
#include "agent-session.h"
#include "../skills/skills-manager.h"
#include "../agents-md/agents-md-manager.h"

#include <cstdlib>
#include <iomanip>
#include <sstream>

// Get the user config directory for llama-agent
static std::string get_config_dir() {
#ifdef _WIN32
const char * appdata = std::getenv("APPDATA");
if (appdata) {
return std::string(appdata) + "\\llama-agent";
}
return "";
#else
const char * home = std::getenv("HOME");
if (home) {
return std::string(home) + "/.llama-agent";
}
return "";
#endif
}

// agent_session implementation

agent_session::agent_session(const std::string & id,
Expand All @@ -21,6 +41,40 @@ agent_session::agent_session(const std::string & id,
permissions_.set_project_root(config_.working_dir);
}
permissions_.set_yolo_mode(config_.yolo_mode);

std::string config_dir = get_config_dir();

// Discover Skills (agentskills.io spec)
if (config_.enable_skills) {
skills_manager skills_mgr;
std::vector<std::string> skill_paths;

// Project-local skills (highest priority)
// Default to "." if working_dir not set, matching CLI behavior
std::string skills_working_dir = config_.working_dir.empty() ? "." : config_.working_dir;
skill_paths.push_back(skills_working_dir + "/.llama-agent/skills");

// User-global skills
if (!config_dir.empty()) {
skill_paths.push_back(config_dir + "/skills");
}

// Extra paths from config
for (const auto & path : config_.extra_skills_paths) {
skill_paths.push_back(path);
}

skills_mgr.discover(skill_paths);
skills_prompt_section_ = skills_mgr.generate_prompt_section();
}

// Discover AGENTS.md files (agents.md spec)
if (config_.enable_agents_md) {
agents_md_manager agents_md_mgr;
std::string working_dir = config_.working_dir.empty() ? "." : config_.working_dir;
agents_md_mgr.discover(working_dir, config_dir);
agents_md_prompt_section_ = agents_md_mgr.generate_prompt_section();
}
}

agent_session::~agent_session() {
Expand Down Expand Up @@ -61,6 +115,15 @@ void agent_session::send_message(const std::string & content,
agent_cfg.working_dir = config_.working_dir;
agent_cfg.yolo_mode = config_.yolo_mode;

// Skills configuration
agent_cfg.enable_skills = config_.enable_skills;
agent_cfg.skills_search_paths = config_.extra_skills_paths;
agent_cfg.skills_prompt_section = skills_prompt_section_;

// AGENTS.md configuration
agent_cfg.enable_agents_md = config_.enable_agents_md;
agent_cfg.agents_md_prompt_section = agents_md_prompt_section_;

loop_ = std::make_unique<agent_loop>(
server_ctx_,
params_,
Expand Down
11 changes: 11 additions & 0 deletions tools/agent/server/agent-session.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ struct agent_session_config {
int tool_timeout_ms = 120000;
std::string working_dir;
std::string system_prompt; // Optional custom system prompt

// Skills configuration (agentskills.io spec)
bool enable_skills = true;
std::vector<std::string> extra_skills_paths;

// AGENTS.md configuration (agents.md spec)
bool enable_agents_md = true;
};

// State of an agent session
Expand Down Expand Up @@ -118,6 +125,10 @@ class agent_session {
// Timestamps
std::chrono::steady_clock::time_point created_at_;
std::chrono::steady_clock::time_point last_activity_;

// Discovered Skills and AGENTS.md content (cached at session creation)
std::string skills_prompt_section_;
std::string agents_md_prompt_section_;
};

// Manages multiple agent sessions
Expand Down