#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <agent_smith/agent_smith.hpp>
namespace
{
std::string envOr(std::string_view name, std::string_view fallback)
{
const char* value = std::getenv(std::string(name).c_str());
return value == nullptr ? std::string(fallback) : std::string(value);
}
void printUsage(std::ostream& output)
{
output << "Usage: agent_smith_demo [--no-mcp | --mcp PROGRAM [ARG...]]\n"
<< "\n"
<< "Environment variables:\n"
<< " LLAMA_SERVER_URL Provider origin "
"(default: http://127.0.0.1:8080)\n"
<< " LLAMA_MODEL Model name (default: local-model)\n"
<< " LLAMA_API_KEY Optional bearer token\n"
<< "\n"
<< "Tools: weather by city and the bundled Python MCP server.\n"
<< "MCP provides addition, example://help, and a greeting prompt.\n"
<< "\n"
<< "Commands: /help, /clear, /stream on, /stream off, /quit\n";
}
agent_smith::ToolSchema weatherSchema()
{
return agent_smith::ToolSchema{
.name = "get_weather",
.description = "Get the current weather for a city.",
.parameters =
{
{"type", "object"},
{"properties", {{"city", {{"type", "string"}}}}},
{"required", {"city"}},
{"additionalProperties", false},
},
};
}
mw::Task<mw::E<agent_smith::ToolResult>>
queryWeather(nlohmann::json arguments, agent_smith::ToolContext context)
{
if(context.runtime == nullptr)
{
co_return std::unexpected(
mw::runtimeError("weather tool needs a runtime"));
}
const auto city = arguments.find("city");
if(city == arguments.end() || !city->is_string() || city->empty())
{
co_return std::unexpected(
mw::runtimeError("weather tool requires a non-empty city"));
}
const std::string city_name = *city;
const std::string url =
"https://wttr.in/" + mw::URL::encode(city_name) + "?format=3";
auto response =
co_await context.runtime->http().get(mw::HTTPRequest(url));
if(!response)
{
co_return std::unexpected(response.error());
}
if(response->status != 200)
{
co_return std::unexpected(
mw::runtimeError("weather service returned HTTP " +
std::to_string(response->status)));
}
co_return agent_smith::ToolResult{
.content = std::string(response->payloadAsStr()),
};
}
void printToolResult(const agent_smith::ToolMessage& result)
{
std::cout << (result.is_error ? "[tool error: " : "[tool result: ")
<< result.content << "]\n";
}
void printUsageReport(const agent_smith::Message& message)
{
const auto* assistant =
std::get_if<agent_smith::AssistantMessage>(&message);
if(assistant != nullptr && assistant->usage)
{
std::cout << "[usage: prompt=" << assistant->usage->prompt
<< ", completion=" << assistant->usage->completion
<< ", total=" << assistant->usage->total << "]\n";
}
}
void printStreamEvent(const agent_smith::StreamEvent& event)
{
std::visit(
[](const auto& value)
{
using Event = std::decay_t<decltype(value)>;
if constexpr(std::is_same_v<Event, agent_smith::StreamTokenDelta>)
{
std::cout << value.text << std::flush;
}
else if constexpr(std::is_same_v<
Event, agent_smith::StreamToolCallStarted>)
{
std::cout << "\n[tool call " << value.index << ": "
<< value.name << "]\n";
}
else if constexpr(std::is_same_v<
Event, agent_smith::StreamToolCallCompleted>)
{
std::cout << "[tool arguments " << value.index << ": "
<< value.arguments.dump() << "]\n";
}
else
{
std::cout << '\n';
}
},
event);
}
} // namespace
int main(int argc, char* argv[])
{
if(argc > 1 && std::string_view(argv[1]) == "--help")
{
printUsage(std::cout);
return 0;
}
const std::string endpoint_text =
envOr("LLAMA_SERVER_URL", "http://127.0.0.1:8080");
auto endpoint = mw::URL::fromStr(endpoint_text);
if(!endpoint)
{
std::cerr << "Invalid LLAMA_SERVER_URL: " << endpoint.error().msg()
<< '\n';
return 2;
}
agent_smith::LlmClientConfig config{
.endpoint = std::move(*endpoint),
.model = envOr("LLAMA_MODEL", "local-model"),
};
if(const char* api_key = std::getenv("LLAMA_API_KEY"); api_key != nullptr)
{
config.api_key = api_key;
}
agent_smith::Runtime runtime;
agent_smith::LlmClient client(runtime, std::move(config));
// Retain at most 64 messages and approximately 8K tokens of history.
auto memory = std::make_shared<agent_smith::RollingWindowMemory>(
agent_smith::RollingWindowConfig{
.max_messages = 64,
.max_tokens = 8192,
});
auto tree = std::make_shared<agent_smith::AgentTreeState>();
agent_smith::Agent agent(runtime, client, memory,
{.max_iterations = 16, .tree_state = tree});
auto registered = agent.addTool(
agent_smith::makeTool(weatherSchema(), queryWeather));
if(!registered)
{
std::cerr << "Tool registration failed: "
<< registered.error().msg() << '\n';
return 2;
}
agent.hooks().on_stream_event = printStreamEvent;
agent.hooks().on_tool_result = printToolResult;
agent.hooks().on_message = printUsageReport;
if(argc <= 1 || std::string_view(argv[1]) != "--no-mcp")
{
std::vector<std::string> command{
"python3", AGENT_SMITH_DEMO_MCP_SERVER};
if(argc > 1)
{
if(std::string_view(argv[1]) != "--mcp" || argc < 3)
{
printUsage(std::cerr);
return 2;
}
command.assign(argv + 2, argv + argc);
}
agent_smith::mcp::ServerConfig mcp_config{
.alias = "demo",
.transport = agent_smith::mcp::StdioServerConfig{
std::move(command)},
};
auto connected = agent_smith::syncWait(
agent.connectMcp(std::move(mcp_config)));
if(!connected)
{
std::cerr << "MCP connection failed: " << connected.error().msg()
<< "\nUse --no-mcp to run without an MCP server.\n";
return 2;
}
std::cout << "[MCP demo connected]\n";
}
std::cout << "Agent Smith " << agent_smith::VERSION
<< " agent CLI. Enter /help for commands.\n";
bool stream = true;
std::string prompt;
while(std::cout << "> " << std::flush, std::getline(std::cin, prompt))
{
if(prompt == "/quit")
{
break;
}
if(prompt == "/help")
{
printUsage(std::cout);
continue;
}
if(prompt == "/clear")
{
memory->clear();
tree->iterations.store(0);
std::cout << "Conversation cleared.\n";
continue;
}
if(prompt == "/stream on" || prompt == "/stream off")
{
stream = prompt == "/stream on";
std::cout << "Streaming " << (stream ? "on" : "off") << ".\n";
continue;
}
if(prompt.empty())
{
continue;
}
// Each interactive turn starts a fresh tree iteration budget.
tree->iterations.store(0);
auto result = agent_smith::syncWait(agent.send(
agent_smith::AgentRequest{
.message = {prompt},
.stream = stream,
}));
if(!result)
{
std::cerr << "\nRequest failed: " << result.error().msg()
<< "\nUse /clear to discard this conversation.\n";
continue;
}
if(!stream)
{
std::cout << result->content << '\n';
}
}
return 0;
}