#include "agent_smith/detail/tool_dispatch.hpp"
#include "agent_smith/detail/gather_all.hpp"
#include "agent_smith/detail/timeout.hpp"
namespace agent_smith::internal
{
struct ToolAccess
{
static std::shared_ptr<Tool> retain(ToolRegistry& registry,
const std::string& name)
{
const auto found = registry.by_name_.find(name);
return found == registry.by_name_.end() ? nullptr : found->second.tool;
}
};
namespace
{
mw::Task<mw::E<ToolResult>> invokeOwned(std::shared_ptr<Tool> tool,
nlohmann::json arguments,
ToolContext context)
{
co_return co_await tool->invoke(std::move(arguments), std::move(context));
}
mw::Task<mw::E<ToolResult>> unknownTool(std::string name)
{
co_return std::unexpected(mw::runtimeError("unknown tool: " + name));
}
std::string errorContent(const std::string& message)
{
return nlohmann::json({{"error", true}, {"message", message}}).dump();
}
} // namespace
mw::Task<mw::E<std::vector<ToolMessage>>> dispatchTools(
Runtime& runtime, ToolRegistry& registry, std::vector<ToolCall> calls,
AgentConfig config, std::optional<std::string>* final_message)
{
std::vector<StartedTask<ToolResult>> started;
started.reserve(calls.size());
for(const auto& call : calls)
{
auto tool = ToolAccess::retain(registry, call.name);
if(tool == nullptr)
{
started.push_back(StartedTask<ToolResult>::start(
unknownTool(call.name)));
continue;
}
const auto budget = registry.options(call.name).timeout.value_or(
config.default_tool_timeout);
CancellationSource cancel(config.tree_state
? config.tree_state->cancel.token() : CancellationToken{});
ToolContext context{
.cancel = cancel.token(),
.budget = budget,
.call_id = call.id,
.logger = spdlog::default_logger_raw(),
.runtime = &runtime,
};
started.push_back(StartedTask<ToolResult>::start(withTimeout(
runtime, invokeOwned(std::move(tool), call.arguments,
std::move(context)),
budget, &cancel)));
}
auto results = co_await gatherAllStarted(std::move(started));
std::vector<ToolMessage> messages;
messages.reserve(calls.size());
for(std::size_t index = 0; index < calls.size(); ++index)
{
auto& result = results[index];
const bool is_error = !result || result->is_error;
if(!is_error && final_message != nullptr && !*final_message &&
result->structured.is_object())
{
const auto& data = result->structured;
if(data.contains("_done") && data["_done"] == true &&
data.contains("final_message") && data["final_message"].is_string())
{
*final_message = data["final_message"].get<std::string>();
}
}
std::string content = !result ? errorContent(result.error().msg())
: result->is_error ? errorContent(result->content)
: std::move(result->content);
if(content.size() > config.tool_result_max_bytes)
{
const auto original_size = content.size();
auto end = config.tool_result_max_bytes;
// Do not split a UTF-8 code point in a serializable message.
while(end > 0 &&
(static_cast<unsigned char>(content[end]) & 0xc0) == 0x80)
{
--end;
}
content.resize(end);
content += "\n…[truncated, original size " +
std::to_string(original_size) + " bytes]";
}
messages.push_back(ToolMessage{
.tool_call_id = calls[index].id,
.name = calls[index].name,
.content = std::move(content),
.is_error = is_error,
});
}
co_return messages;
}
} // namespace agent_smith::internal